<commit_before>var apidocs = require('multilang-apidocs')
var fs = require('fs')
var path = require('path')
var cp = require('child_process')
var _ = require('lodash')
var debug = require('debug')('thought:helpers')
var minimatch = require('minimatch')
var glob = require('glob')
var findPackage = require('find-package')
var Handlebars = require('handlebars')
var qfs = require('q-io/fs')
var util = require('util')
var collect = require('stream-collect')
var Q = require('q')

module.exports = {
  /**
   * Use JsDoc and JsDox to create markdown output of jsdoc-comments.
   * This only works for javascript-files
   * @param {string} globPattern a glob-pattern to find the files
   * @param {string} headerPrefix a string such as '##' that is use as prefix for lines starting with '#' to reduced the header-size
   */
  jsdoc: function (globPattern, headerPrefix) {
    var jsdoc2md = require('jsdoc-to-markdown')
    return collect(jsdoc2md({src: globPattern}))
      .then(function (output) {
        var markdown = output.toString('utf-8')
        return markdown.replace(/^#/mg, headerPrefix + '#')
      })
  },

  /**
   * Display an object as indented JSON-String.
   * This is mainly for testing purposes when adapting templates
   * @param {object} obj the object
   * @returns {string} JSON.stringify()
   */
  json: function (obj) {
    return '```json\n' + JSON.stringify(obj, null, 2) + '\n```\n'
  },

  /**
   * Include the apidocs from a file. The `multilang-apidocs` package
   * is used to extract the comments from the file.
   * @param {string} a glob-pattern to find the files to analyze
   * @returns {string}
   */
  apidocs: function (globPattern) {
    var files = glob.sync(globPattern)
    debug('apidoc files', files)
    return files.map(function (file) {
      var comments = apidocs(fs.readFileSync(file, 'utf-8'), {
        filename: file
      })
      debug('extracted comments', comments)
      return comments.map(_.property('markdown')).join('\n')
    }).join('\n')
  },

  /**
   * Include a file into a markdown code-block
   * @param filename
   * @param language the programming language used for the code-block
   * @returns {string}
   */
  include: function (filename, language) {
    return qfs.read(filename).then(function (contents) {
      return '```' +
        (_.isString(language) ? language : path.extname(filename).substr(1)) +
        '\n' +
        contents +
        '\n```\n'
    })
  },

  /**
   * Directly include a file.
   * @param filename
   */
  includeRaw: function (filename) {
    return qfs.read(filename)
  },

  /**
   * Includes an example file into the template, replacing `require()` calls to the current
   * module by `require('module-name')` (only single-quotes are replaced)
   * If your file is `examples/example.js`, you would do
   * ```js
   * var fn = require('../')
   * ```
   * to load your module. That way, you get an executable script.
   * The helper will when include
   * ```js
   * var fn = require('module-name')
   * ```
   * in your docs, which is what a user of the module will do.
   *
   * @param {string} filename the name of the example file
   * @param {object} options.hash
   * @param {boolean} options.hash.snippet If set to true, only the lines between
   *    <snip> and </snip> will be included
   * @api public
   */
  example: function (filename, options) {
    return qfs.read(filename)
      .then(function (contents) {
        // Relative path to the current module (e.g. "../"). This path must be replaced
        // by the module name in the
        var modulePath = path.relative(path.dirname(filename), '.') + '/'
        debug('example modulepath', modulePath)
        var requireModuleRegex = new RegExp("require\\('" + _.escapeRegExp(modulePath) + "(.*?)'\\)", 'm')
        if (options && options.hash && options.hash.snippet) {
          contents = contents.match(/---<snip>---.*\n([\S\s]*?)\n.*---<\/snip>---/)[1]
        }

        return util.format('```%s\n%s\n```',
          path.extname(filename).substr(1),
          contents.trim().replace(requireModuleRegex, function (match, suffix) {
            return "require('" + require(process.cwd() + '/package').name + (suffix ? '/' + suffix : '') + "')"
          })
        )
      })
  },

  /**
   * Return true if a file exists
   * @param {string} filename the path to the file
   * @return {boolean} true, if the file or diectory exists
   * @api public
   */
  exists: function (filename) {
    return qfs.exists(filename)
  },

  /**
   * Execute a commad and include the output in a fenced code-block.
   * @param {string} command the command, passed to `child-process#execSync()`
   * @param {string} options.hash.lang the language tag that should be attached to the fence
   *    (like `js` or `bash`). If this is set to `raw`, the output is included as-is, without fences.
   * @param {string} options.hash.cwd the current working directory of the example process
   * @returns {string} the output of `execSync`, enclosed in fences.
   * @api public
   */
  exec: function (command, options) {
    var start
    var end
    var lang = options.hash && options.hash.lang
    switch (lang) {
      case 'raw':
        start = end = ''
        break
      case 'inline':
        start = end = '`'
        break
      default:
        var fenceLanguage = _.isString(lang) ? lang : ''
        start = '```' + fenceLanguage + '\n'
        end = '\n```'
    }
    var output = cp.execSync(command, {
      encoding: 'utf8',
      cwd: options.hash && options.hash.cwd
    })
    return start + output.trim() + end
  },

  /**
   * Return a drawing of a directory tree (using [archy](https://www.npmjs.com/package/archy))
   * @param {string} globPattern a pattern describing all files and directories to include into the tree-view.
   * @param {string=} baseDir the base directory from which the `globPattern` is applied.
   * @returns {string} a display of the directory tree of the selected files and directories.
   * @api public
   */
  dirTree: function (baseDir, globPattern) {
    // Is basedir is not a string, it is probably the handlebars "options" object
    if (!_.isString(globPattern)) {
      globPattern = '**'
    }
    var defer = Q.defer()
    glob(globPattern, {cwd: baseDir}, function (err, files) {
      debug('dirTree glob result', files)
      files.sort()
      // Split paths into components
      var components = files.map(function (file) {
        return _.compact(file.split(path.sep))
      })
      if (components.length == 0) {
        defer.reject("Cannot find a single file for '"+globPattern+"' in '"+baseDir+"'");
        return;
      }
      var treeObject = treeFromPathComponents(components);
      var tree = require('archy')(treeObject)
      defer.fulfill('
\n' + tree + '
')
    })
    return defer.promise
  },

  /**
   * Render an object hierarchy
   * @param object
   * @param options
   * @param {function} options.fn computes the label for a node based on the node itself
   * @returns {string}
   */
  renderTree: function (object, options) {
    var tree = require('archy')(transformTree(object, options.fn))
    return '
\n' + tree + '
'
  },

  /**
   * Block helper that executes the block in the current context but sets to special variables:
   *
   * * The github-url of the given file in the current package version is stored into `@url`
   * * The `package.json` of the file's module is stored into `@package`
   * @param filePath file that is used to find that package.json
   * @param options block-helper options
   */
  withPackageOf: function (filePath, options) {
    var packageJson = findPackage(path.resolve(filePath), true)
    var url = packageJson && packageJson.repository && packageJson.repository.url
    var version = packageJson.version
    // path within the package
    var relativePath = path.relative(path.dirname(packageJson.paths.absolute), filePath)

    if (options.data) {
      var data = Handlebars.createFrame(options.data || {})
      // Build url to correct version and file in github
      if (url && url.match(/github.com/)) {
        data.url = url.replace(/^git\+/, '').replace(/\.git$/, '') + '/blob/v' + version + '/' + relativePath
      }
      data['package'] = packageJson
    }
    return options.fn(this, {data: data})
  },

  /**
   * Create a link to the npm-package of a package
   * @param packageName the name of the package
   */
  npm: function (packageName) {
    return '[' + packageName + '](https://npmjs.com/package/' + packageName + ')'

  },

  /**
   * Replace all characters that may not be used in HTML id-attributes by '-'.
   * There is still the restriction that IDs may only start with letters, which
   * is not addressed by this helper.
   */
  'htmlId': function (value) {
    return value.replace(/[^A-Za-z0-9-_:.]/g, '').toLowerCase()
  }
}

/**
 * Transform a tree-structure of the form
 * ```
 * {
 *   prop1: 'value',
 *   prop2: 'value',
 *   ...,
 *   children: [
 *     {
 *        prop1: 'value',
 *        propt2: 'value',
 *        ...,
 *        children: ...
 *     }
 *   ]
 * }
 * ```
 * Into an [archy](https://www.npmjs.com/package/archy)-compatible format, by passing each node to a block-helper function.
 * The result of the function should be a string which is then used as label for the node.
 *
 * @param {object} object the tree data
 * @param fn the block-helper function (options.fn) of Handlebars (http://handlebarsjs.com/block_helpers.html)
 */
function transformTree(object, fn) {
  var label = fn(object).trim();
  if (object.children) {
    return {
      label: label,
      nodes: object.children.map(function (child) {
        return transformTree(child, fn);
      })
    }
  } else {
    return label;
  }
}

/**
 * Transform an array of path components into an [archy](https://www.npmjs.com/package/archy)-compatible tree structure.
 *
 * ```
 * [ [ 'abc', 'cde', 'efg' ], [ 'abc','cde','abc'], ['abc','zyx'] ]
 * ```
 *
 * becomes
 *
 * ```
 * {
 *   label: 'abc',
 *   nodes: [
        {
          label: 'cde',
          nodes: [
            'efg',
            'abc'
          ]
        },
        'zyx'
 *   ]
 * }
 * ```
 *
 * Nodes with a single subnode are collapsed and the resulting node gets the label `node/subnode`.
 *
 * @param {string[][]} files an array of filenames, split by `path.sep`
 * @param {string} label the label for the current tree node
 * @returns {object} a tree structure as needed by [archy](https://www.npmjs.com/package/archy)
 */
function treeFromPathComponents(files, label) {
  debug('treeFromPathComponents', files, label)
  if (files.length == 0) {
    return label
  }
  var result = {
    label: label,
    nodes: _(files)
      .groupBy('0')
      .map(function (group, key) {
        var values = group
          .map(function (item) {
            return item.slice(1)
          })
          .filter(function (item) {
            return item.length > 0
          })
        return treeFromPathComponents(values, key)
      }).value()
  }

  // Condense path if directory only has one entry
  if (result.nodes.length == 1 && _.isPlainObject(result.nodes[0])) {
    return {
      label: (result.label ? result.label + '/' : '') + result.nodes[0].label,
      nodes: result.nodes[0].nodes
    }
  } else {
    return result
  }

}
<commit_msg>Added "github" helper to create links to github files.<commit_after>var apidocs = require('multilang-apidocs')
var fs = require('fs')
var path = require('path')
var cp = require('child_process')
var _ = require('lodash')
var debug = require('debug')('thought:helpers')
var minimatch = require('minimatch')
var glob = require('glob')
var findPackage = require('find-package')
var Handlebars = require('handlebars')
var qfs = require('q-io/fs')
var util = require('util')
var collect = require('stream-collect')
var Q = require('q')

module.exports = {
  /**
   * Use JsDoc and JsDox to create markdown output of jsdoc-comments.
   * This only works for javascript-files
   * @param {string} globPattern a glob-pattern to find the files
   * @param {string} headerPrefix a string such as '##' that is use as prefix for lines starting with '#' to reduced the header-size
   */
  jsdoc: function (globPattern, headerPrefix) {
    var jsdoc2md = require('jsdoc-to-markdown')
    return collect(jsdoc2md({src: globPattern}))
      .then(function (output) {
        var markdown = output.toString('utf-8')
        return markdown.replace(/^#/mg, headerPrefix + '#')
      })
  },

  /**
   * Display an object as indented JSON-String.
   * This is mainly for testing purposes when adapting templates
   * @param {object} obj the object
   * @returns {string} JSON.stringify()
   */
  json: function (obj) {
    return '```json\n' + JSON.stringify(obj, null, 2) + '\n```\n'
  },

  /**
   * Include the apidocs from a file. The `multilang-apidocs` package
   * is used to extract the comments from the file.
   * @param {string} a glob-pattern to find the files to analyze
   * @returns {string}
   */
  apidocs: function (globPattern) {
    var files = glob.sync(globPattern)
    debug('apidoc files', files)
    return files.map(function (file) {
      var comments = apidocs(fs.readFileSync(file, 'utf-8'), {
        filename: file
      })
      debug('extracted comments', comments)
      return comments.map(_.property('markdown')).join('\n')
    }).join('\n')
  },

  /**
   * Include a file into a markdown code-block
   * @param filename
   * @param language the programming language used for the code-block
   * @returns {string}
   */
  include: function (filename, language) {
    return qfs.read(filename).then(function (contents) {
      return '```' +
      (_.isString(language) ? language : path.extname(filename).substr(1)) +
      '\n' +
      contents +
      '\n```\n'
    })
  },

  /**
   * Directly include a file.
   * @param filename
   */
  includeRaw: function (filename) {
    return qfs.read(filename)
  },

  /**
   * Includes an example file into the template, replacing `require()` calls to the current
   * module by `require('module-name')` (only single-quotes are replaced)
   * If your file is `examples/example.js`, you would do
   * ```js
   * var fn = require('../')
   * ```
   * to load your module. That way, you get an executable script.
   * The helper will when include
   * ```js
   * var fn = require('module-name')
   * ```
   * in your docs, which is what a user of the module will do.
   *
   * @param {string} filename the name of the example file
   * @param {object} options.hash
   * @param {boolean} options.hash.snippet If set to true, only the lines between
   *    <snip> and </snip> will be included
   * @api public
   */
  example: function (filename, options) {
    return qfs.read(filename)
      .then(function (contents) {
        // Relative path to the current module (e.g. "../"). This path must be replaced
        // by the module name in the
        var modulePath = path.relative(path.dirname(filename), '.') + '/'
        debug('example modulepath', modulePath)
        var requireModuleRegex = new RegExp("require\\('" + _.escapeRegExp(modulePath) + "(.*?)'\\)", 'm')
        if (options && options.hash && options.hash.snippet) {
          contents = contents.match(/---<snip>---.*\n([\S\s]*?)\n.*---<\/snip>---/)[1]
        }

        return util.format('```%s\n%s\n```',
          path.extname(filename).substr(1),
          contents.trim().replace(requireModuleRegex, function (match, suffix) {
            return "require('" + require(process.cwd() + '/package').name + (suffix ? '/' + suffix : '') + "')"
          })
        )
      })
  },

  /**
   * Return true if a file exists
   * @param {string} filename the path to the file
   * @return {boolean} true, if the file or diectory exists
   * @api public
   */
  exists: function (filename) {
    return qfs.exists(filename)
  },

  /**
   * Execute a commad and include the output in a fenced code-block.
   * @param {string} command the command, passed to `child-process#execSync()`
   * @param {string} options.hash.lang the language tag that should be attached to the fence
   *    (like `js` or `bash`). If this is set to `raw`, the output is included as-is, without fences.
   * @param {string} options.hash.cwd the current working directory of the example process
   * @returns {string} the output of `execSync`, enclosed in fences.
   * @api public
   */
  exec: function (command, options) {
    var start
    var end
    var lang = options.hash && options.hash.lang
    switch (lang) {
      case 'raw':
        start = end = ''
        break
      case 'inline':
        start = end = '`'
        break
      default:
        var fenceLanguage = _.isString(lang) ? lang : ''
        start = '```' + fenceLanguage + '\n'
        end = '\n```'
    }
    var output = cp.execSync(command, {
      encoding: 'utf8',
      cwd: options.hash && options.hash.cwd
    })
    return start + output.trim() + end
  },

  /**
   * Return a drawing of a directory tree (using [archy](https://www.npmjs.com/package/archy))
   * @param {string} globPattern a pattern describing all files and directories to include into the tree-view.
   * @param {string=} baseDir the base directory from which the `globPattern` is applied.
   * @returns {string} a display of the directory tree of the selected files and directories.
   * @api public
   */
  dirTree: function (baseDir, globPattern) {
    // Is basedir is not a string, it is probably the handlebars "options" object
    if (!_.isString(globPattern)) {
      globPattern = '**'
    }
    var defer = Q.defer()
    glob(globPattern, {cwd: baseDir}, function (err, files) {
      debug('dirTree glob result', files)
      files.sort()
      // Split paths into components
      var components = files.map(function (file) {
        return _.compact(file.split(path.sep))
      })
      if (components.length == 0) {
        defer.reject("Cannot find a single file for '" + globPattern + "' in '" + baseDir + "'")
        return
      }
      var treeObject = treeFromPathComponents(components)
      var tree = require('archy')(treeObject)
      defer.fulfill('
\n' + tree + '
')
    })
    return defer.promise
  },

  /**
   * Render an object hierarchy
   * @param object
   * @param options
   * @param {function} options.fn computes the label for a node based on the node itself
   * @returns {string}
   */
  renderTree: function (object, options) {
    var tree = require('archy')(transformTree(object, options.fn))
    return '
\n' + tree + '
'
  },

  /**
   * Block helper that executes the block in the current context but sets to special variables:
   *
   * * The github-url of the given file in the current package version is stored into `@url`
   * * The `package.json` of the file's module is stored into `@package`
   * @param filePath file that is used to find that package.json
   * @param options block-helper options
   */
  withPackageOf: function (filePath, options) {
    if (options.data) {
      var data = Handlebars.createFrame(options.data || {})
    }
    data.url = githubUrl(filePath)
    data.package = findPackage(path.resolve(filePath), false)
    return options.fn(this, {data: data})
  },

  github: function(filePath) {
    return githubUrl(filePath)
  },

  /**
   * Create a link to the npm-package of a package
   * @param packageName the name of the package
   */
  npm: function (packageName) {
    return '[' + packageName + '](https://npmjs.com/package/' + packageName + ')'

  },

  /**
   * Replace all characters that may not be used in HTML id-attributes by '-'.
   * There is still the restriction that IDs may only start with letters, which
   * is not addressed by this helper.
   */
  'htmlId': function (value) {
    return value.replace(/[^A-Za-z0-9-_:.]/g, '').toLowerCase()
  }
}

/**
 * Transform a tree-structure of the form
 * ```
 * {
 *   prop1: 'value',
 *   prop2: 'value',
 *   ...,
 *   children: [
 *     {
 *        prop1: 'value',
 *        propt2: 'value',
 *        ...,
 *        children: ...
 *     }
 *   ]
 * }
 * ```
 * Into an [archy](https://www.npmjs.com/package/archy)-compatible format, by passing each node to a block-helper function.
 * The result of the function should be a string which is then used as label for the node.
 *
 * @param {object} object the tree data
 * @param fn the block-helper function (options.fn) of Handlebars (http://handlebarsjs.com/block_helpers.html)
 */
function transformTree (object, fn) {
  var label = fn(object).trim()
  if (object.children) {
    return {
      label: label,
      nodes: object.children.map(function (child) {
        return transformTree(child, fn)
      })
    }
  } else {
    return label
  }
}

/**
 * Transform an array of path components into an [archy](https://www.npmjs.com/package/archy)-compatible tree structure.
 *
 * ```
 * [ [ 'abc', 'cde', 'efg' ], [ 'abc','cde','abc'], ['abc','zyx'] ]
 * ```
 *
 * becomes
 *
 * ```
 * {
 *   label: 'abc',
 *   nodes: [
        {
          label: 'cde',
          nodes: [
            'efg',
            'abc'
          ]
        },
        'zyx'
 *   ]
 * }
 * ```
 *
 * Nodes with a single subnode are collapsed and the resulting node gets the label `node/subnode`.
 *
 * @param {string[][]} files an array of filenames, split by `path.sep`
 * @param {string} label the label for the current tree node
 * @returns {object} a tree structure as needed by [archy](https://www.npmjs.com/package/archy)
 */
function treeFromPathComponents (files, label) {
  debug('treeFromPathComponents', files, label)
  if (files.length == 0) {
    return label
  }
  var result = {
    label: label,
    nodes: _(files)
      .groupBy('0')
      .map(function (group, key) {
        var values = group
          .map(function (item) {
            return item.slice(1)
          })
          .filter(function (item) {
            return item.length > 0
          })
        return treeFromPathComponents(values, key)
      }).value()
  }

  // Condense path if directory only has one entry
  if (result.nodes.length == 1 && _.isPlainObject(result.nodes[0])) {
    return {
      label: (result.label ? result.label + '/' : '') + result.nodes[0].label,
      nodes: result.nodes[0].nodes
    }
  } else {
    return result
  }

}

function githubUrl (filePath) {
  // Build url to correct version and file in github
  var packageJson = findPackage(path.resolve(filePath), true)
  var url = packageJson && packageJson.repository && packageJson.repository.url
  if (url && url.match(/github.com/)) {
    var version = packageJson.version
    // path within the package
    var relativePath = path.relative(path.dirname(packageJson.paths.absolute), filePath)
    return url.replace(/^git\+/, '').replace(/\.git$/, '') + '/blob/v' + version + '/' + relativePath
  }
}
<|endoftext|>


<commit_before>/*
 Copyright 2018 Carmilla Mina Jankovic

 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.
 */

const eventCentral = require('./EventCentral');

class AccessCentral {
  constructor() {
    this.AccessLevels = {
      ANONYMOUS: 0,
      STANDARD: 1,
      PRIVILEGED: 2,
      MODERATOR: 3,
      ADMIN: 4,
      SUPERUSER: 5,
      GOD: 6,
    };
    this.accessElements = {};

    eventCentral.addWatcher({
      event: eventCentral.Events.ACCESS_CHANGE,
      func: ({ accessLevel }) => {
        Object.keys(this.accessElements).forEach((level) => {
          const levelElements = this.accessElements[level] || [];

          levelElements.forEach((accessElement) => {
            const {
              minimumAccessLevel,
              maxAccessLevel,
              element,
            } = accessElement;

            if (accessLevel >= minimumAccessLevel && accessLevel <= maxAccessLevel) {
              element.classList.remove('hide');
            } else {
              element.classList.add('hide');
            }
          });
        });
      },
    });
  }

  addAccessElement({
    element,
    minimumAccessLevel = 0,
    maxAccessLevel = 999,
  }) {
    if (!this.accessElements[minimumAccessLevel]) {
      this.accessElements[minimumAccessLevel] = [];
    }

    this.accessElements[minimumAccessLevel].push({
      element,
      minimumAccessLevel,
      maxAccessLevel,
    });
  }

  removeAccessElement({
    minimumAccessLevel,
    element,
  }) {
    if (!this.accessElements[minimumAccessLevel]) {
      this.accessElements[minimumAccessLevel] = [];
    }

    const levelElements = this.accessElements[minimumAccessLevel];

    levelElements.splice(levelElements.findIndex((accessElement) => {
      return accessElement.element === element;
    }), 1);
  }

  /**
   * Checks if user has access, is admin or can see the object.
   * @param {Object} params Parameter.
   * @param {Object} params.objectToAccess Object to access.
   * @param {Object} params.toAuth Object to auth.
   * @returns {boolean} Does the user have access to the object?
   */
  hasAccessTo({
    objectToAccess,
    toAuth,
  }) {
    const {
      teamIds = [],
      userIds = [],
      userAdminIds = [],
      teamAdminIds = [],
      ownerId,
      isPublic,
      visibility,
    } = objectToAccess;
    const {
      hasFullAccess,
      accessLevel = this.AccessLevels.ANONYMOUS,
      objectId: authUserId,
      teamIds: authTeamIds = [],
      aliases = [],
    } = toAuth;

    const userHasAccess = userIds.concat([ownerId]).includes(authUserId);
    const teamHasAccess = teamIds.find(teamId => authTeamIds.includes(teamId));
    const aliasHasAccess = aliases.find(aliasId => userIds.includes(aliasId));
    const userHasAdminAccess = userAdminIds.includes(authUserId);
    const aliasHasAdminAccess = aliases.find(aliasId => userAdminIds.includes(aliasId));
    const teamHasAdminAccess = teamAdminIds.find(adminId => authTeamIds.includes(adminId));
    const isAdmin = ownerId === authUserId || hasFullAccess || accessLevel >= this.AccessLevels.ADMIN;

    return {
      canSee: isAdmin || isPublic || userHasAccess || teamHasAccess || aliasHasAccess || accessLevel >= visibility,
      hasAccess: isAdmin || isPublic || userHasAccess || teamHasAccess || aliasHasAccess,
      hasFullAccess: isAdmin || userHasAdminAccess || teamHasAdminAccess || aliasHasAdminAccess,
    };
  }
}

const accessCentral = new AccessCentral();

module.exports = accessCentral;
<commit_msg>Null check<commit_after>/*
 Copyright 2018 Carmilla Mina Jankovic

 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.
 */

const eventCentral = require('./EventCentral');

class AccessCentral {
  constructor() {
    this.AccessLevels = {
      ANONYMOUS: 0,
      STANDARD: 1,
      PRIVILEGED: 2,
      MODERATOR: 3,
      ADMIN: 4,
      SUPERUSER: 5,
      GOD: 6,
    };
    this.accessElements = {};

    eventCentral.addWatcher({
      event: eventCentral.Events.ACCESS_CHANGE,
      func: ({ accessLevel }) => {
        Object.keys(this.accessElements).forEach((level) => {
          const levelElements = this.accessElements[level] || [];

          levelElements.forEach((accessElement) => {
            const {
              minimumAccessLevel,
              maxAccessLevel,
              element,
            } = accessElement;

            if (accessLevel >= minimumAccessLevel && accessLevel <= maxAccessLevel) {
              element.classList.remove('hide');
            } else {
              element.classList.add('hide');
            }
          });
        });
      },
    });
  }

  addAccessElement({
    element,
    minimumAccessLevel = 0,
    maxAccessLevel = 999,
  }) {
    if (!this.accessElements[minimumAccessLevel]) {
      this.accessElements[minimumAccessLevel] = [];
    }

    this.accessElements[minimumAccessLevel].push({
      element,
      minimumAccessLevel,
      maxAccessLevel,
    });
  }

  removeAccessElement({
    minimumAccessLevel,
    element,
  }) {
    if (!this.accessElements[minimumAccessLevel]) {
      this.accessElements[minimumAccessLevel] = [];
    }

    const levelElements = this.accessElements[minimumAccessLevel];

    levelElements.splice(levelElements.findIndex((accessElement) => {
      return accessElement.element === element;
    }), 1);
  }

  /**
   * Checks if user has access, is admin or can see the object.
   * @param {Object} params Parameter.
   * @param {Object} params.objectToAccess Object to access.
   * @param {Object} params.toAuth Object to auth.
   * @returns {boolean} Does the user have access to the object?
   */
  hasAccessTo({
    objectToAccess,
    toAuth,
  }) {
    if (!objectToAccess || !toAuth) {
      return {
        canSee: false,
        hasFullAccess: false,
        hasAccess: false,
      };
    }

    const {
      teamIds = [],
      userIds = [],
      userAdminIds = [],
      teamAdminIds = [],
      ownerId,
      isPublic,
      visibility,
    } = objectToAccess;
    const {
      hasFullAccess,
      accessLevel = this.AccessLevels.ANONYMOUS,
      objectId: authUserId,
      teamIds: authTeamIds = [],
      aliases = [],
    } = toAuth;

    const userHasAccess = userIds.concat([ownerId]).includes(authUserId);
    const teamHasAccess = teamIds.find(teamId => authTeamIds.includes(teamId));
    const aliasHasAccess = aliases.find(aliasId => userIds.includes(aliasId));
    const userHasAdminAccess = userAdminIds.includes(authUserId);
    const aliasHasAdminAccess = aliases.find(aliasId => userAdminIds.includes(aliasId));
    const teamHasAdminAccess = teamAdminIds.find(adminId => authTeamIds.includes(adminId));
    const isAdmin = ownerId === authUserId || hasFullAccess || accessLevel >= this.AccessLevels.ADMIN;

    return {
      canSee: isAdmin || isPublic || userHasAccess || teamHasAccess || aliasHasAccess || accessLevel >= visibility,
      hasAccess: isAdmin || isPublic || userHasAccess || teamHasAccess || aliasHasAccess,
      hasFullAccess: isAdmin || userHasAdminAccess || teamHasAdminAccess || aliasHasAdminAccess,
    };
  }
}

const accessCentral = new AccessCentral();

module.exports = accessCentral;
<|endoftext|>


<commit_before>/**
 * Created by Administrator on 2016/11/3.
 */
$(function(){
    /*inputstart*/
    var $input = $("#header .search input");
    $input.on("focus",function() {
        if (this.value == this.defaultValue) {
            this.value = "";
        }

    }).on('blur',function() {
        if (this.value == '') {
            this.value = this.defaultValue;
        }
    })
    $input.on("keypress",function(event){
        if ( event.which == 13 && this.value!="") {
            alert(this.value);
        }
    });
    /*inputend*/
    /*任ɫstart*/
    var $skinLi = $("#skin li");
    $skinLi.on("click",function(){
        $("#"+this.id).addClass("selected").siblings().removeClass("selected");
        $("#cssfile").attr("href","css/skin/"+ this.id +".css"); //òͬƤ
    });
    /*任ɫend*/
    /*ֲͼstart*/
    var timer;
    var $aImg = $("#img-turns a");
    /*$aImg.each(function(index){
        $(this).css({"z-index":$aImg.size()-index}) ;
    });*/
    $aImg.mouseenter(function(){
        clearInterval(timer);
    });
    var $imgLi = $("#img-turns .img-link li");
    var iNow = 0;
    $imgLi.hover(function(){
        changeImg($(this).index());
        iNow = $(this).index();
        clearInterval(timer);
    },function(){
        run();
    });
    function changeImg(index){
        $imgLi.eq(index).addClass("selected").siblings().removeClass('selected');
        $aImg.eq(index).stop(true).fadeIn().siblings().hide();
    }
    function run(){
        timer = setInterval(function(){
            iNow++;
            if(iNow == $imgLi.size()){
                iNow = 0;
            }
            changeImg(iNow);
        },2000);
    }
    run();
    /*ֲͼend*/
    /*ֲͼstart*/
    var $tabContent = $("#tab-content");
    var contentWidth = $tabContent.width();
    $tabContent.width(4 * contentWidth);
    $tabContent.html($tabContent.html() + $tabContent.html() + $tabContent.html() + $tabContent.html());
    var $tabA = $("#tab a");
    $("#tab li").mouseover(function(){
        $(this).addClass("selected").siblings().removeClass("selected");
        $tabA.removeClass("select");
        $tabA.eq($(this).index()).addClass("select");
        $tabContent.animate({
            left:-$(this).index() * contentWidth
        },1000);
    });
    /*ֲͼend*/

});<commit_msg>index的js部分优化了<commit_after>/**
 * Created by Administrator on 2016/11/3.
 */
$(function(){

    /*轮播图start*/
    var timer;
    var $imgTurns = $("#img-turns");
    var $aImg = $("a",$imgTurns);
    /*$aImg.each(function(index){
        $(this).css({"z-index":$aImg.size()-index}) ;
    });*/
    $aImg.mouseenter(function(){
        clearInterval(timer);
    });
    var $imgLi = $(".img-link li",$imgTurns);
    var iNow = 0;
    $imgLi.hover(function(){
        changeImg($(this).index());
        iNow = $(this).index();
        clearInterval(timer);
    },function(){
        run();
    });
    function changeImg(index){
        $imgLi.eq(index).addClass("selected").siblings().removeClass('selected');
        $aImg.eq(index).stop(true).fadeIn().siblings().hide();
    }
    function run(){
        timer = setInterval(function(){
            iNow++;
            if(iNow == $imgLi.size()){
                iNow = 0;
            }
            changeImg(iNow);
        },2000);
    }
    run();
    /*轮播图end*/
    /*下面的轮播图start*/
    var $tabContent = $("#tab-content");
    var contentWidth = $tabContent.width();
    $tabContent.width(4 * contentWidth);
    $tabContent.html($tabContent.html() + $tabContent.html() + $tabContent.html() + $tabContent.html());
    var $tab = $("#tab");
    var $tabA = $("a",$tab);
    $("li",$tab).mouseover(function(){
        $(this).addClass("selected").siblings().removeClass("selected");
        $tabA.removeClass("select");
        $tabA.eq($(this).index()).addClass("select");
        $tabContent.animate({
            left:-$(this).index() * contentWidth
        },1000);
    });
    /*下面的轮播图end*/

});<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 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.
 */

CLASS({
  package: 'foam.persistence',
  name: 'ObjectReplicator',
  properties: [
    {
      name: 'id',
      swiftType: 'String',
      swiftDefaultValue: '""',
      swiftPostSet: function() {/*
        // Re-set the dao so it listens for the new id.
        self.obj = nil
        self.set("dao", value: self.dao)

        self.future = Future()
        self.dao.find(newValue, sink: ClosureSink(args: [
          "putFn": FoamFunction(fn: { (args) -> AnyObject? in
            self.future.set(args[0])
            return nil
          })
        ]))
        self.future.get { o in
          self.obj = o as? FObject
        }
      */},
    },
    {
      name: 'model',
    },
    {
      name: 'dao',
      swiftType: 'AbstractDAO',
      swiftPostSet: function() {/*
        if let o = oldValue as? AbstractDAO {
          o.unlisten(self.daoListener)
        }
        newValue.`where`(EQ(self.pk, arg2: self.id)).listen(self.daoListener)
      */},
    },
    {
      type: 'Boolean',
      name: 'feedback',
      defaultValue: false,
      swiftDefaultValue: 'false',
    },
    {
      name: 'objectListener',
      swiftType: 'PropertyChangeListener',
      swiftFactory: function() {/*
        return PropertyChangeListener(callback: {
            obj, prop, oldValue, newValue in
          self.objChanged()
        })
      */},
    },
    {
      name: 'obj',
      swiftType: 'FObject?',
      swiftPostSet: function() {/*
        if let o = oldValue as? FObject {
          o.removeListener(self.objectListener)
        }
        newValue?.addListener(self.objectListener)
      */},
    },
    {
      name: 'pk',
      lazyFactory: function() {
        // TODO: Support multi part keys
        return this.model.getProperty(this.model.ids[0]);
      },
      swiftType: 'String',
      swiftDefaultValue: '"id"',
    },
    {
      name: 'future',
      lazyFactory: function() {
        var self = this;
        var fut = afuture();
        this.dao.find(self.id, {
          put: function(obj) {
            fut.set(obj);
          }
        });
        fut.get(function(o) {
          self.obj = o;
          self.attach();
        })
        return fut;
      },
      swiftType: 'Future',
    },
    {
      name: 'daoListener',
      factory: function() {
        return {
          put: this.onPut,
          remove: this.onRemove
        };
      },
      swiftType: 'Sink',
      swiftFactory: function() {/*
        return ClosureSink(args: [
          "putFn": FoamFunction(fn: { (args) -> AnyObject? in
            self.onPut()
            return nil
          }),
          "removeFn": FoamFunction(fn: { (args) -> AnyObject? in
            self.onRemove()
            return nil
          })
        ])
      */},
    }
  ],
  methods: [
    {
      name: 'destroy',
      code: function() {
        this.obj.removeListener(this.objChanged);
        this.dao.unlisten(this.daoListener);
      },
      swiftCode: function() {/*
        self.obj?.removeListener(self.objectListener)
        self.dao.unlisten(self.daoListener)
      */},
    },
    {
      name: 'attach',
      code: function attach() {
        this.obj.addListener(this.objChanged);
        this.dao.where(EQ(this.pk, this.obj.id)).listen(this.daoListener)
      },
    },
  ],
  listeners: [
    {
      name: 'objChanged',
      code: function() {
        if ( this.feedback )
          return;

        var clone = this.obj.deepClone();
        this.dao.put(clone, {
          put: function(obj2) {
            this.feedback = true;
            this.obj.copyFrom(obj2);
            this.feedback = false;
          }.bind(this)
        });
      },
      swiftCode: function() {/*
        if feedback { return }
        let clone = obj!.deepClone();
        dao.put(clone, sink: ClosureSink(args: [
          "putFn": FoamFunction(fn: { (args) -> AnyObject? in
            let obj2 = args[0]
            self.feedback = true;
            self.obj!.copyFrom(obj2);
            self.feedback = false;
            return nil
          })
        ]))
      */},
    },
    {
      name: 'onPut',
      code: function() {
        this.dao.find(this.id, {
          put: function(obj2) {
            if ( ! this.obj ) {
              this.future.set(obj2);
            } else {
              this.feedback = true;
              this.obj.copyFrom(obj2);
              this.feedback = false;
            }
          }.bind(this)
        });
      },
      swiftCode: function() {/*
        dao.find(id, sink: ClosureSink(args: [
          "putFn": FoamFunction(fn: { (args) -> AnyObject? in
            let obj2 = args[0] as! FObject
            if self.obj == nil {
              self.future.set(obj2);
            } else {
              self.feedback = true;
              self.obj!.copyFrom(obj2);
              self.feedback = false;
            }
            return nil
          })
        ]))
      */},
    },
    {
      name: 'onRemove',
      code: function() {
        // TODO
      },
      swiftCode: '// TODO',
    }
  ]
});
<commit_msg>For swift, give the object replicator's future a factory and only replace it if it has been set.<commit_after>/**
 * @license
 * Copyright 2015 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.
 */

CLASS({
  package: 'foam.persistence',
  name: 'ObjectReplicator',
  properties: [
    {
      name: 'id',
      swiftType: 'String',
      swiftDefaultValue: '""',
      swiftPostSet: function() {/*
        // Re-set the dao so it listens for the new id.
        self.obj = nil
        self.set("dao", value: self.dao)

        if self.future.set {
          self.future = Future()
        }
        self.dao.find(newValue, sink: ClosureSink(args: [
          "putFn": FoamFunction(fn: { (args) -> AnyObject? in
            self.future.set(args[0])
            return nil
          })
        ]))
        self.future.get { o in
          self.obj = o as? FObject
        }
      */},
    },
    {
      name: 'model',
    },
    {
      name: 'dao',
      swiftType: 'AbstractDAO',
      swiftPostSet: function() {/*
        if let o = oldValue as? AbstractDAO {
          o.unlisten(self.daoListener)
        }
        newValue.`where`(EQ(self.pk, arg2: self.id)).listen(self.daoListener)
      */},
    },
    {
      type: 'Boolean',
      name: 'feedback',
      defaultValue: false,
      swiftDefaultValue: 'false',
    },
    {
      name: 'objectListener',
      swiftType: 'PropertyChangeListener',
      swiftFactory: function() {/*
        return PropertyChangeListener(callback: {
            obj, prop, oldValue, newValue in
          self.objChanged()
        })
      */},
    },
    {
      name: 'obj',
      swiftType: 'FObject?',
      swiftPostSet: function() {/*
        if let o = oldValue as? FObject {
          o.removeListener(self.objectListener)
        }
        newValue?.addListener(self.objectListener)
      */},
    },
    {
      name: 'pk',
      lazyFactory: function() {
        // TODO: Support multi part keys
        return this.model.getProperty(this.model.ids[0]);
      },
      swiftType: 'String',
      swiftDefaultValue: '"id"',
    },
    {
      name: 'future',
      lazyFactory: function() {
        var self = this;
        var fut = afuture();
        this.dao.find(self.id, {
          put: function(obj) {
            fut.set(obj);
          }
        });
        fut.get(function(o) {
          self.obj = o;
          self.attach();
        })
        return fut;
      },
      swiftType: 'Future',
      swiftFactory: 'return Future()',
    },
    {
      name: 'daoListener',
      factory: function() {
        return {
          put: this.onPut,
          remove: this.onRemove
        };
      },
      swiftType: 'Sink',
      swiftFactory: function() {/*
        return ClosureSink(args: [
          "putFn": FoamFunction(fn: { (args) -> AnyObject? in
            self.onPut()
            return nil
          }),
          "removeFn": FoamFunction(fn: { (args) -> AnyObject? in
            self.onRemove()
            return nil
          })
        ])
      */},
    }
  ],
  methods: [
    {
      name: 'destroy',
      code: function() {
        this.obj.removeListener(this.objChanged);
        this.dao.unlisten(this.daoListener);
      },
      swiftCode: function() {/*
        self.obj?.removeListener(self.objectListener)
        self.dao.unlisten(self.daoListener)
      */},
    },
    {
      name: 'attach',
      code: function attach() {
        this.obj.addListener(this.objChanged);
        this.dao.where(EQ(this.pk, this.obj.id)).listen(this.daoListener)
      },
    },
  ],
  listeners: [
    {
      name: 'objChanged',
      code: function() {
        if ( this.feedback )
          return;

        var clone = this.obj.deepClone();
        this.dao.put(clone, {
          put: function(obj2) {
            this.feedback = true;
            this.obj.copyFrom(obj2);
            this.feedback = false;
          }.bind(this)
        });
      },
      swiftCode: function() {/*
        if feedback { return }
        let clone = obj!.deepClone();
        dao.put(clone, sink: ClosureSink(args: [
          "putFn": FoamFunction(fn: { (args) -> AnyObject? in
            let obj2 = args[0]
            self.feedback = true;
            self.obj!.copyFrom(obj2);
            self.feedback = false;
            return nil
          })
        ]))
      */},
    },
    {
      name: 'onPut',
      code: function() {
        this.dao.find(this.id, {
          put: function(obj2) {
            if ( ! this.obj ) {
              this.future.set(obj2);
            } else {
              this.feedback = true;
              this.obj.copyFrom(obj2);
              this.feedback = false;
            }
          }.bind(this)
        });
      },
      swiftCode: function() {/*
        dao.find(id, sink: ClosureSink(args: [
          "putFn": FoamFunction(fn: { (args) -> AnyObject? in
            let obj2 = args[0] as! FObject
            if self.obj == nil {
              self.future.set(obj2);
            } else {
              self.feedback = true;
              self.obj!.copyFrom(obj2);
              self.feedback = false;
            }
            return nil
          })
        ]))
      */},
    },
    {
      name: 'onRemove',
      code: function() {
        // TODO
      },
      swiftCode: '// TODO',
    }
  ]
});
<|endoftext|>


<commit_before>import Component from 'flarum/Component';
import icon from 'flarum/helpers/icon';

export default class UploadButton extends Component {

    /**
    * Load the configured remote uploader service.
    */
    init() {
        // the service type handling uploads
        this.type = app.forum.attribute('flagrow.image-upload.uploadMethod') || 'local';
        this.textAreaObj = null;

        this.button = this;
    }

    /**
    * Show the actual Upload Button.
    *
    * @returns {*}
    */
    view() {
        return m('div', {className: 'Button hasIcon flagrow-image-upload-button Button--icon'}, [
            icon('picture-o', {className: 'Button-icon'}),
            m('span', {className: 'Button-label'}, app.translator.trans('flagrow-image-upload.forum.buttons.attach')),
            m('input', {
                type: 'file',
                accept: 'image/*',
                name: 'flagrow-image-upload-input',
                onchange: this.process.bind(this)
            })
        ]);
    }

    /**
    * Process the upload event.
    */
    process() {
        // keep track of the parent object
        var button = this.button;

        // wheter the image should be resized
        this.mustResize = app.forum.attribute('flagrow.image-upload.mustResize') || false;
        // max width and height of the uploaded image
        this.maxWidth = app.forum.attribute('flagrow.image-upload.resizeMaxWidth') || null;
        this.maxHeight = app.forum.attribute('flagrow.image-upload.resizeMaxHeight') || null;
        // set the default value (changed later if necessary)
        this.scalingFactor = 1;

        // create the object that is going to load the image from the user's computer
        var reader = new FileReader;
        // this is what the reader is going to do once the file has been loaded into the object
        reader.onload = function(e) {
            // show loader
            button.markLoaderStarted();

            // create an off-screen canvas and an Image object
            var canvas = document.createElement('canvas'),
            canvasContext = canvas.getContext('2d'),
            imageObject = new Image;

            // this is what we are going to do once the image has been loaded into the object
            imageObject.onload = function() {

                // evaluate the scalingFactor to keep aspect ratio
                if(button.mustResize == true) {
                    button.scalingFactor = Math.min((button.maxWidth/imageObject.width), (button.maxHeight/imageObject.height), 1);
                }

                // set canvas' dimension to target size
                canvas.width = imageObject.width*button.scalingFactor;
                canvas.height = imageObject.height*button.scalingFactor;

                // draw source image into the off-screen canvas:
                canvasContext.drawImage(imageObject, 0, 0, canvas.width, canvas.height);

                // encode image to data-uri with base64 version of compressed image
                var resizedImage = canvas.toDataURL();

                // this formats the file for base64 upload
                var data = resizedImage.substr(resizedImage.indexOf(",") + 1, resizedImage.length);

                // run the service (oauth or the admin specified one)
                button[button.type](data);
            };

            // load the file in the image object
            imageObject.src = e.target.result;
        };

        // actually runs everything on the file that has been selected
        reader.readAsDataURL($("input[name='flagrow-image-upload-input']")[0].files[0]);
    }

    local(imageData) {

        const data = new FormData();
        data.append('image', imageData);

        app.request({
            method: 'POST',
            url: app.forum.attribute('apiUrl') + '/image/upload',
            serialize: raw => raw,
            data
        }).then(
            this.success.bind(this),
            this.failure.bind(this)
        );
    }

    /**
    * Imgur upload method. Uses oauth.
    *
    * @param imageData
    */
    imgur(imageData) {
        //checks if the imgur client-id is defined
        if(app.forum.attribute('flagrow.image-upload.imgurClientId') !== '') {
            // create the imgur specific parameters that are going to be passed to
            // the ouath method.
            const connectionParameters = {
                'endpoint' : 'https://api.imgur.com/3/image',
                'headers' : {
                    Authorization: 'Client-ID ' + app.forum.attribute('flagrow.image-upload.imgurClientId')
                }
            }

            // now we call the oauth method.
            this.oauth(imageData, connectionParameters);
        } else {
            // otherwise throws an error
            this.markLoaderFailed();
            // reset the button after 2s
            setTimeout(function() {
                this.resetLoader();
            }.bind(this), 2000);
            // also logs the error for debug
            console.log('Flagrow Image Upload Extension: the Imgur client-id was not provided');
        }

    }

    /**
    * oauth general upload method.
    *
    * @param imageData
    * @param connectionParameters
    */
    oauth(imageData, connectionParameters = {}) {
        // api endpoint
        this.endpoint = connectionParameters.endpoint || app.forum.attribute('flagrow.image-upload.endpoint');
        // client id
        this.client_id = connectionParameters.client_id || app.forum.attribute('flagrow.image-upload.clientId');
        // client bearer token if non-anonymous
        this.token = connectionParameters.token || app.forum.attribute('flagrow.image-upload.token') || null;
        // whether uploading is anonymous, not account bound
        this.isAnonymous = connectionParameters.anonymous || app.forum.attribute('flagrow.image-upload.anonymous') || true;

        if (this.isAnonymous || !this.token) {
            var headers = {
                client_id: this.client_id
            };
        } else {
            var headers = {
                bearer: this.token
            };
        }

        var button = this;

        $.ajax(this.endpoint, {
            method: 'post',
            // authorization and other headers
            headers: connectionParameters.headers || headers,
            data: {
                'image': imageData,
                'type': 'base64'
            },
            // upload success
            success: function (payload, statusCode, xhr) {
                // redirects the call to the success method of the button
                // also replaces `http` with `https`
                button.success(payload.data.link.replace('http:', 'https:'));
            },
            // upload error
            error: function(xhr, statusText, error) {
                button.failure(statusText);
            },
            statusCode: {
                // unauthorized
                401: function () {
                    button.failure('Unauthorized');
                }
            }
        });
    }

    /**
     * Handles errors.
     *
     * @param message
     */
    failure(message) {
        this.markLoaderFailed();
        // todo show popup
    }

    /**
     * Appends the link to the body of the composer.
     *
     * @param link
     */
    success(link) {

        if(typeof link == 'object') {
            link = link.data.attributes.url;
        }

        this.markLoaderSuccess();

        // create a markdown string that holds the image link
        var markdownString = '\n![image ' + link + '](' + link + ')\n';

        // place the Markdown image link in the Composer
        this.textAreaObj.insertAtCursor(markdownString);

        // if we are not starting a new discussion, the variable is defined
        if (typeof this.textAreaObj.props.preview !== 'undefined') {
            // show what we just uploaded
            this.textAreaObj.props.preview();
        }

        var button = this;
        // reset the button for a new upload
        setTimeout(function() {
            button.resetLoader();
        }, 1000);
    }

    /**
    * Sets the icon classes of the icon in the upload input field.
    *
    * @param classes
    */
    setIconClasses(classes) {
        $('.flagrow-image-upload-button > i').removeClass('fa-picture-o fa-spin fa-circle-o-notch fa-check green fa-times red').addClass(classes);
    }

    /**
    * Sets the label of the uploader button and allows disabling submits.
    *
    * @param text
    * @param disable
    */
    setLabel(text, disable = false) {

        // set the text of the button
        $('.flagrow-image-upload-button > .Button-label').text(text);

        // get the composer element, so we search only in it
        var composer = $('#composer');
        // enable on timeout
        if((disable === false) && ($('.item-submit > button', composer).prop('disabled') === true)) {
            setTimeout(function() {
                $('.item-submit > button', composer).prop('disabled', false);
            }, 1000);
        } else if(disable === true) {
            $('.item-submit > button', composer).prop('disabled', true);
        }
    }

    /**
    * Modifies the file upload button to indicate upload started.
    */
    markLoaderStarted() {
        this.setIconClasses('fa-spin fa-circle-o-notch');
        this.setLabel(app.translator.trans('flagrow-image-upload.forum.states.loading'), true);
    }

    /**
    * Modifies the file upload button to indicate upload success.
    */
    markLoaderSuccess() {
        this.setIconClasses('fa-check green');
        this.setLabel(app.translator.trans('flagrow-image-upload.forum.states.success'), false);
    }

    /**
    * Modifies the file upload button to indicate upload failed.
    */
    markLoaderFailed() {
        this.setIconClasses('fa-times red');
        this.setLabel(app.translator.trans('flagrow-image-upload.forum.states.error'), false);
    }

    /**
    * Resets the file upload button to its original state.
    */
    resetLoader() {
        this.setIconClasses('fa-picture-o');
        this.setLabel(app.translator.trans('flagrow-image-upload.forum.buttons.attach'), false);
        // remove the old file url
        $("input[name='flagrow-image-upload-input']").val("");
    }
}
<commit_msg>unneeded<commit_after><|endoftext|>


<commit_before>// Copyright 2016, University of Colorado Boulder

/**
 *
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */

// modules
var copyDirectory = require( '../../../chipper/js/grunt/copyDirectory' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

module.exports = function( grunt, buildConfig ) {
  'use strict';

  // TODO: chipper#101 eek, this is scary! we are importing from the repository dir. ideally we should just have uglify-js installed once in chipper?
  var uglify = require( '../../../' + buildConfig.name + '/node_modules/uglify-js' );

  // output the SimIFrameClient.js and WrapperUtils.js to the top level lib (not password-protected), see https://github.com/phetsims/phet-io/issues/353
  grunt.file.mkdir( 'build/lib' );

  var filterWrapper = function( abspath, contents ) {
    var originalContents = contents + '';

    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', buildConfig.name );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', buildConfig.version );
    }
    if ( abspath.indexOf( '.html' ) >= 0 ) {

      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/lodash-2.4.1.min.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/font-awesome-4.5.0/css/font-awesome.min.css"',
        '"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/jquery-2.1.0.min.js"',
        '"https://code.jquery.com/jquery-2.2.3.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/jquery-ui-1.8.24.min.js"',
        '"https://code.jquery.com/ui/1.8.24/jquery-ui.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/d3-4.2.2.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"'
      );

      // This returns the whole line that contains this substring, so it can be removed
      var firstLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js"' );
      if ( firstLine ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstLine, '' );
      }

      // For info about phetio.js, see the end of this file
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phetio.js}}-->',
        '<script type="text/javascript" src="../../lib/phetio.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script type="text/javascript" src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico">'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<script type="text/javascript" src="../../../assert/js/assert.js"></script>',
        '<script>' + grunt.file.read( '../assert/js/assert.js' ) + '</script>'
      );
      //template uses exclusively "

      contents = ChipperStringUtils.replaceAll( contents, '../../js/', '../../lib/' );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };
  copyDirectory( grunt, '../phet-io/wrappers', 'build/wrappers', filterWrapper );

  var devguideHTML = grunt.file.read( '../phet-io-website/root/devguide/index.html' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/bootstrap-3.3.6-dist/css/bootstrap.min.css', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/bootstrap-3.3.6-dist/js/bootstrap.min.js', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/font-awesome-4.5.0/css/font-awesome.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/js/jquery-1.12.3.min.js', 'https://code.jquery.com/jquery-1.12.3.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/css/', './css/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/js/', './js/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/highlight.js-9.1.0/styles/tomorrow-night-bright.css', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/tomorrow-night-bright.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/highlight.js-9.1.0/highlight.js', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/favicon.ico', './favicon.ico' );
  grunt.file.write( 'build/docs/devguide.html', devguideHTML );
  copyDirectory( grunt, '../phet-io-website/root/assets/css', 'build/docs/css', filterWrapper );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io.js', './build/docs/js/phet-io.js' );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io-ga.js', './build/docs/js/phet-io-ga.js' );
  grunt.file.copy( '../phet-io-website/root/assets/favicon.ico', './build/docs/favicon.ico' );

  // Minify phet libraries into 'lib/phetio.js'
  var DESTINATION_PATH = 'build/lib';
  var OUTPUT_FILE = 'phetio.js';
  var COPYRIGHT_HEADER = '// Copyright 2002-2016, University of Colorado Boulder\n' +
                         '// This PhET-iO file requires a license\n' +
                         '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                         '// For licensing, please contact phethelp@colorado.edu';

  // Determine which sim versions will be pointed to by WrapperUtils
  var PRECODE = 'window.useRelativeSimPath=true;';

  var LIB_FILES = [
    '../query-string-machine/js/QueryStringMachine.js',
    '../phet-io/wrappers/common/js/SimIFrameClient.js',
    '../phet-io/wrappers/common/js/assert.js',
    '../phet-io/wrappers/common/js/WrapperUtils.js' ];

  var minified = uglify.minify( LIB_FILES, {
    mangle: true,
    output: {
      inline_script: true, // escape </script
      beautify: false
    },
    compress: {
      global_defs: {}
    }
  } ).code;

  grunt.file.write( DESTINATION_PATH + '/' + OUTPUT_FILE, COPYRIGHT_HEADER + '\n\n' + PRECODE + minified );

};<commit_msg>Removed unused PRECODE, see https://github.com/phetsims/phet-io/issues/879<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 *
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */

// modules
var copyDirectory = require( '../../../chipper/js/grunt/copyDirectory' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

module.exports = function( grunt, buildConfig ) {
  'use strict';

  // TODO: chipper#101 eek, this is scary! we are importing from the repository dir. ideally we should just have uglify-js installed once in chipper?
  var uglify = require( '../../../' + buildConfig.name + '/node_modules/uglify-js' );

  // output the SimIFrameClient.js and WrapperUtils.js to the top level lib (not password-protected), see https://github.com/phetsims/phet-io/issues/353
  grunt.file.mkdir( 'build/lib' );

  var filterWrapper = function( abspath, contents ) {
    var originalContents = contents + '';

    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', buildConfig.name );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', buildConfig.version );
    }
    if ( abspath.indexOf( '.html' ) >= 0 ) {

      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/lodash-2.4.1.min.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/font-awesome-4.5.0/css/font-awesome.min.css"',
        '"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/jquery-2.1.0.min.js"',
        '"https://code.jquery.com/jquery-2.2.3.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/jquery-ui-1.8.24.min.js"',
        '"https://code.jquery.com/ui/1.8.24/jquery-ui.min.js"'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '"../../../sherpa/lib/d3-4.2.2.js"',
        '"https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"'
      );

      // This returns the whole line that contains this substring, so it can be removed
      var firstLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js"' );
      if ( firstLine ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstLine, '' );
      }

      // For info about phetio.js, see the end of this file
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phetio.js}}-->',
        '<script type="text/javascript" src="../../lib/phetio.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script type="text/javascript" src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico">'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<script type="text/javascript" src="../../../assert/js/assert.js"></script>',
        '<script>' + grunt.file.read( '../assert/js/assert.js' ) + '</script>'
      );
      //template uses exclusively "

      contents = ChipperStringUtils.replaceAll( contents, '../../js/', '../../lib/' );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };
  copyDirectory( grunt, '../phet-io/wrappers', 'build/wrappers', filterWrapper );

  var devguideHTML = grunt.file.read( '../phet-io-website/root/devguide/index.html' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/bootstrap-3.3.6-dist/css/bootstrap.min.css', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/bootstrap-3.3.6-dist/js/bootstrap.min.js', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/font-awesome-4.5.0/css/font-awesome.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/js/jquery-1.12.3.min.js', 'https://code.jquery.com/jquery-1.12.3.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/css/', './css/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/js/', './js/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/highlight.js-9.1.0/styles/tomorrow-night-bright.css', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/tomorrow-night-bright.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/highlight.js-9.1.0/highlight.js', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '/assets/favicon.ico', './favicon.ico' );
  grunt.file.write( 'build/docs/devguide.html', devguideHTML );
  copyDirectory( grunt, '../phet-io-website/root/assets/css', 'build/docs/css', filterWrapper );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io.js', './build/docs/js/phet-io.js' );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io-ga.js', './build/docs/js/phet-io-ga.js' );
  grunt.file.copy( '../phet-io-website/root/assets/favicon.ico', './build/docs/favicon.ico' );

  // Minify phet libraries into 'lib/phetio.js'
  var DESTINATION_PATH = 'build/lib';
  var OUTPUT_FILE = 'phetio.js';
  var COPYRIGHT_HEADER = '// Copyright 2002-2016, University of Colorado Boulder\n' +
                         '// This PhET-iO file requires a license\n' +
                         '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                         '// For licensing, please contact phethelp@colorado.edu';

  var LIB_FILES = [
    '../query-string-machine/js/QueryStringMachine.js',
    '../phet-io/wrappers/common/js/SimIFrameClient.js',
    '../phet-io/wrappers/common/js/assert.js',
    '../phet-io/wrappers/common/js/WrapperUtils.js' ];

  var minified = uglify.minify( LIB_FILES, {
    mangle: true,
    output: {
      inline_script: true, // escape </script
      beautify: false
    },
    compress: {
      global_defs: {}
    }
  } ).code;

  grunt.file.write( DESTINATION_PATH + '/' + OUTPUT_FILE, COPYRIGHT_HEADER + '\n\n' + minified );
};<|endoftext|>


<commit_before>/*
 *   Copyright 2015 OSBI
 */

/**
 * Changes the locale of the cube
 */
var ChangeLocale = Backbone.View.extend({

    initialize: function (args) {
        this.workspace = args.workspace;

        // Create a unique ID for use as the CSS selector
        this.id = _.uniqueId("changeLocale_");
        $(this.el).attr({ id: this.id });

        // Bind table rendering to query result event
        _.bindAll(this, "render", "show", "handleClick", "processDatasourcesWithoutAddingNewTab");

        this.datasources = new Connections({}, { dialog: this });

        // Add change locale button to saiku toolbar
        this.add_button();
        this.workspace.toolbar.changeLocale = this.show;

        // Create locale screen
        this.localeOptionsScreen = $("
  " +
            "
" +
            "
         <button id='en_US' > English </button>
 " +
            "
   	    <button id='nl_BE' > Dutch </button>
 " +
            "
   	    <button id='fr_FR' > French </button>
" +
            "
" +
            "
 " +
            "
 Choose your language
")
        ;

        // attach event handler
        this.localeOptionsScreen.find('button').click(this.handleClick);

        //function for menu
        $("#nav ul ").css({display: "none"}); // Opera Fix
        $("#nav li").hover(function () {
            $(this).find('ul:first').css({visibility: "visible", display: "none"}).show(400);
        }, function () {
            $(this).find('ul:first').css({visibility: "hidden"});
        });

        // Append chart to workspace
        $(this.workspace.el).find('.workspace_results')
            .prepend($(this.el).hide())
            .prepend(this.localeOptionsScreen.hide());

    },

    add_button: function () {
        var $chart_button =
            $('')
                .css({  'background-image': "url('js/saiku/plugins/ChangeLocale/images/change_locale.png')",
                    'background-repeat': 'no-repeat',
                    'background-position': '7px 7px'
                });

        var $chart_li = $('

').append($chart_button);
        $(this.workspace.toolbar.el).find("ul").append($chart_li);
    },

    show: function (event, ui) {
        this.toggleWorkspaceWithLocaleScreen();
        if ($(event.target).hasClass('on')) {
            this.render();
        } else {
            this.workspace.table.render({ data: this.workspace.query.result.lastresult() });
        }
    },

    toggleWorkspaceWithLocaleScreen: function () {
        $(this.workspace.table.el).toggle();
        $(this.el).toggle();
        $(this.localeOptionsScreen).toggle();
        $(event.target).toggleClass('on');
    },

    handleClick: function (event) {
        // Keep a reference to the main plugin object.
        var this_p = this;

        // get selected locale
        var newLocale = $(event.target).attr('id');

        // get current selected connection name (without URL data)
        var selectedCube = $(".cubes option:selected").val();
        var selectedConnectionName = selectedCube.substring(0, selectedCube.indexOf("/"));

        // Get all connections from back-end
        var getUrl = Settings.REST_URL + "admin" + "/datasources";
        $.get(getUrl, function (data) {
            var allConnections = data;
            // match
            var selectedConnection = _.find(allConnections, function (connection) {
                return connection.connectionname == selectedConnectionName
            });

            var selectedDataSource = new DataSource(selectedConnection);

            var localeChanged = this_p.changeLocale(selectedConnection, selectedDataSource, newLocale);
            if (localeChanged) {
                this_p.saveDataSource(selectedDataSource);
                this_p.refreshDatasources();
            }
        });
        return false;
    },

    changeLocale: function (selectedConnection, selectedDataSource, newLocale) {
        var localeChanged;
        if (selectedConnection.advanced == null) {
            alert("Change the URL connection string to advanced");
            localeChanged = false;
        } else {
            var referenceText = "locale=";
            var start = selectedConnection.advanced.toLowerCase().indexOf(referenceText);
            start = start + referenceText.length;
            if (start == -1) {
                alert("no locale defined in connection string of data source")
                localeChanged = false;
            }
            else {
                var end = selectedConnection.advanced.indexOf(";", start);
                var oldLocale = selectedConnection.advanced.substring(start, end);
                selectedDataSource.set({"advanced": selectedConnection.advanced.replace(oldLocale, newLocale)});
                localeChanged = true;
            }
        }
        return localeChanged;
    },

    saveDataSource: function (selectedDataSource) {
        selectedDataSource.save({}, {
                data: JSON.stringify(selectedDataSource.attributes),
                contentType: "application/json",
                success: function (model, respose, options) {
                    console.log("The model has been updated to the server");
                },
                error: function (model, xhr, options) {
                    console.log("Something went wrong while updating the model: " + xhr.responseText);
                }
            }
        );
    },

    refreshDatasources: function () {
        // set feedback to notice users datasources are refreshing

        var this_plugin = this;
        if (typeof localStorage !== "undefined" && localStorage) {
            localStorage.clear();
        }

        Saiku.session.sessionworkspace.clear();

        if (typeof localStorage !== "undefined" && localStorage) {
            localStorage.setItem('saiku-version', Settings.VERSION);
        }

        Saiku.session.sessionworkspace.fetch({success: this_plugin.processDatasourcesWithoutAddingNewTab}, {});
    },

    processDatasourcesWithoutAddingNewTab: function (model, response) {
        // Save session in localStorage for other tabs to use
        if (typeof localStorage !== "undefined" && localStorage && localStorage.getItem('session') === null) {
            localStorage.setItem('session', JSON.stringify(response));

            // Set expiration on localStorage to one day in the future
            var expires = (new Date()).getTime() + Settings.LOCALSTORAGE_EXPIRATION;
            if (typeof localStorage !== "undefined" && localStorage) {
                localStorage.setItem('expiration', expires);
            }
        }

        // Generate cube navigation for reuse
        Saiku.session.sessionworkspace.cube_navigation = _.template($("#template-cubes").html())({
            connections: response
        });

        // Create cube objects
        Saiku.session.sessionworkspace.cube = {};
        Saiku.session.sessionworkspace.connections = response;
        _.delay(Saiku.session.sessionworkspace.prefetch_dimensions, 20);

        this.toggleWorkspaceWithLocaleScreen();
    }
});

var DataSource = Backbone.Model.extend({
//    url: "admin/datasources",
    urlRoot: "admin/datasources",
    refresh: function () {
        $.ajax({
            type: 'GET',
            url: Settings.REST_URL + "admin" + "/datasources/" + this.get("connectionname") + "/refresh"
        });
    }
});

/**
 * Start Plugin
 */
Saiku.events.bind('session:new', function (session) {

    function new_workspace(args) {
        // Add stats element
        if (typeof args.workspace.changeLocale == "undefined") {
            args.workspace.changeLocale = new ChangeLocale({ workspace: args.workspace });
        }
    };

    function clear_workspace(args) {
        if (typeof args.workspace.changeLocale != "undefined") {
            $(args.workspace.changeLocale.localeOptionsScreen).hide();
            $(args.workspace.changeLocale.el).parents().find('.workspace_results table').show();
            $(args.workspace.changeLocale.el).hide();
        }
    };

    // Attach stats to existing tabs
    for (var i = 0; i < Saiku.tabs._tabs.length; i++) {
        var tab = Saiku.tabs._tabs[i];
        new_workspace({
            workspace: tab.content
        });
    }
    ;

    // Attach stats to future tabs
    Saiku.session.bind("workspace:new", new_workspace);
    Saiku.session.bind("workspace:clear", clear_workspace);
});
<commit_msg>ChangeLocale: provide user feedback<commit_after>/*
 *   Copyright 2015 OSBI
 */

/**
 * Changes the locale of the cube
 */
var ChangeLocale = Backbone.View.extend({

    initialize: function (args) {
        this.visible = false;

        this.workspace = args.workspace;

        // Create a unique ID for use as the CSS selector
        this.id = _.uniqueId("changeLocale_");
        $(this.el).attr({ id: this.id });

        // Bind table rendering to query result event
        _.bindAll(this, "render", "show", "handleClick", "processDatasourcesWithoutAddingNewTab", "toggleLocaleScreen", "setUserFeedback");

        this.datasources = new Connections({}, { dialog: this });

        // Add change locale button to saiku toolbar
        this.add_button();
        this.workspace.toolbar.changeLocale = this.show;

        // Create locale screen
        this.localeOptionsScreen = $("
  " +
            "
" +
            "
         <button id='en_US' > English </button>
 " +
            "
   	    <button id='nl_BE' > Dutch </button>
 " +
            "
   	    <button id='fr_FR' > French </button>
" +
            "
" +
            "
 " +
            " Choose your language ")
        ;

        // attach event handler
        this.localeOptionsScreen.find('button').click(this.handleClick);

        //function for menu
        $("#nav ul ").css({display: "none"}); // Opera Fix
        $("#nav li").hover(function () {
            $(this).find('ul:first').css({visibility: "visible", display: "none"}).show(400);
        }, function () {
            $(this).find('ul:first').css({visibility: "hidden"});
        });

        // Append chart to workspace
        $(this.workspace.el).find('.workspace_results')
            .prepend($(this.el).hide())
            .prepend(this.localeOptionsScreen.hide());

    },

    add_button: function () {
        var $chart_button =
            $('')
                .css({  'background-image': "url('js/saiku/plugins/ChangeLocale/images/change_locale.png')",
                    'background-repeat': 'no-repeat',
                    'background-position': '7px 7px'
                });

        var $chart_li = $('

').append($chart_button);
        $(this.workspace.toolbar.el).find("ul").append($chart_li);
    },

    show: function (event, ui) {
        this.toggleWorkspaceWithLocaleScreen();
//        if ($(event.target).hasClass('on')) {
//            this.render();
//        } else {
//            this.workspace.table.render({ data: this.workspace.query.result.lastresult() });
//        }
    },

    toggleWorkspaceWithLocaleScreen: function () {
        this.toggleWorkSpace();
        this.toggleLocaleScreen();
    },

    toggleWorkSpace: function () {
        $(this.workspace.table.el).toggle();
        $(this.el).toggle();
    },

    toggleLocaleScreen: function () {
        if (this.visible) {
            this.setUserFeedback("Choose language");
            $(this.workspace.el).find('#languageOptions').addClass('hide');
            $(this.workspace.el).find('#feedback').addClass('hide');
            this.workspace.toolbar.run_query();
        }
        else {
            $(this.workspace.el).find('#languageOptions').removeClass('hide').show();
            $(this.workspace.el).find('#feedback').removeClass('hide').show();
        }
        $(event.target).toggleClass('on');
        this.visible = !this.visible;
    },

    handleClick: function (event) {
        // Keep a reference to the main plugin object.
        var this_p = this;
        this_p.setUserFeedback("Changing locale...");
        // get selected locale
        var newLocale = $(event.target).attr('id');

        // get current selected connection name (without URL data)
        var selectedCube = $(".cubes option:selected").val();
        var selectedConnectionName = selectedCube.substring(0, selectedCube.indexOf("/"));

        // Get all connections from back-end
        var getUrl = Settings.REST_URL + "admin" + "/datasources";
        $.get(getUrl, function (data) {
            var allConnections = data;
            // match
            var selectedConnection = _.find(allConnections, function (connection) {
                return connection.connectionname == selectedConnectionName
            });

            var selectedDataSource = new DataSource(selectedConnection);

            var localeChanged = this_p.changeLocale(selectedConnection, selectedDataSource, newLocale);
            if (localeChanged) {
                this_p.setUserFeedback("Adding locale to data source...");
                this_p.saveDataSource(selectedDataSource);
                this_p.setUserFeedback("Refreshing data source with new locale...");
                this_p.refreshDatasources();
            }
        });
        return false;
    },

    changeLocale: function (selectedConnection, selectedDataSource, newLocale) {
        var localeChanged;
        if (selectedConnection.advanced == null) {
            this.setUserFeedback("Change the URL connection string to advanced");
            localeChanged = false;
        } else {
            var referenceText = "locale=";
            var start = selectedConnection.advanced.toLowerCase().indexOf(referenceText);
            start = start + referenceText.length;
            if (start == -1) {
                this.setUserFeedback("no locale defined in connection string of data source");
                localeChanged = false;
            }
            else {
                var end = selectedConnection.advanced.indexOf(";", start);
                var oldLocale = selectedConnection.advanced.substring(start, end);
                selectedDataSource.set({"advanced": selectedConnection.advanced.replace(oldLocale, newLocale)});
                localeChanged = true;
            }
        }
        return localeChanged;
    },

    saveDataSource: function (selectedDataSource) {
        var this_plugin = this;
        selectedDataSource.save({}, {
                data: JSON.stringify(selectedDataSource.attributes),
                contentType: "application/json",
                success: function (model, respose, options) {
//                    this_plugin.setUserFeedback("The model has been updated to the server");
                },
                error: function (model, xhr, options) {
                    this_plugin.setUserFeedback("Something went wrong while updating the model: " + xhr.responseText);
                }
            }
        );
    },

    refreshDatasources: function () {
        // keep reference to this plugin
        var this_plugin = this;
        if (typeof localStorage !== "undefined" && localStorage) {
            localStorage.clear();
        }

        Saiku.session.sessionworkspace.clear();

        if (typeof localStorage !== "undefined" && localStorage) {
            localStorage.setItem('saiku-version', Settings.VERSION);
        }

        Saiku.session.sessionworkspace.fetch({success: this_plugin.processDatasourcesWithoutAddingNewTab}, {});
    },

    processDatasourcesWithoutAddingNewTab: function (model, response) {
        // Save session in localStorage for other tabs to use
        if (typeof localStorage !== "undefined" && localStorage && localStorage.getItem('session') === null) {
            localStorage.setItem('session', JSON.stringify(response));

            // Set expiration on localStorage to one day in the future
            var expires = (new Date()).getTime() + Settings.LOCALSTORAGE_EXPIRATION;
            if (typeof localStorage !== "undefined" && localStorage) {
                localStorage.setItem('expiration', expires);
            }
        }

        // Generate cube navigation for reuse
        Saiku.session.sessionworkspace.cube_navigation = _.template($("#template-cubes").html())({
            connections: response
        });

        // Create cube objects
        Saiku.session.sessionworkspace.cube = {};
        Saiku.session.sessionworkspace.connections = response;
        _.delay(Saiku.session.sessionworkspace.prefetch_dimensions, 20);

        this.show();
    },

    setUserFeedback: function (msg) {
        $("#feedback").html(msg);
    }
});

var DataSource = Backbone.Model.extend({
//    url: "admin/datasources",
    urlRoot: "admin/datasources",
    refresh: function () {
        $.ajax({
            type: 'GET',
            url: Settings.REST_URL + "admin" + "/datasources/" + this.get("connectionname") + "/refresh"
        });
    }
});

/**
 * Start Plugin
 */
Saiku.events.bind('session:new', function (session) {

    function new_workspace(args) {
        // Add stats element
        if (typeof args.workspace.changeLocale == "undefined") {
            args.workspace.changeLocale = new ChangeLocale({ workspace: args.workspace });
        }
    };

    function clear_workspace(args) {
        if (typeof args.workspace.changeLocale != "undefined") {
            $(args.workspace.changeLocale.localeOptionsScreen).hide();
            $(args.workspace.changeLocale.el).parents().find('.workspace_results table').show();
            $(args.workspace.changeLocale.el).hide();
        }
    };

    // Attach stats to existing tabs
    for (var i = 0; i < Saiku.tabs._tabs.length; i++) {
        var tab = Saiku.tabs._tabs[i];
        new_workspace({
            workspace: tab.content
        });
    }
    ;

    // Attach stats to future tabs
    Saiku.session.bind("workspace:new", new_workspace);
    Saiku.session.bind("workspace:clear", clear_workspace);
});
<|endoftext|>