<commit_before>'use strict';

var paramFilter = {
    name         : 'filter',
    required     : false,
    defaultValue : 'assigned',
    type         : 'enum',
    location     : 'query',
    description  : 'Indicates which sorts of issues to return.',
    enumValues   : [
        'assigned',
        'created',
        'mentioned',
        'subscribed',
        'all'
    ]
};

var paramState = {
    name         : 'state',
    required     : false,
    defaultValue : 'open',
    type         : 'enum',
    location     : 'query',
    description  : 'Indicates the state of the issues to return.',
    enumValues   : [
        'open',
        'closed',
        'all'
    ]
};

var paramLabels = {
    name         : 'labels',
    required     : false,
    type         : 'string',
    location     : 'query',
    description  : 'A list of comma separated label names. Example: `bug,ui,@high`'
};

var paramSort = {
    name         : 'sort',
    required     : false,
    defaultValue : 'created',
    type         : 'enum',
    location     : 'query',
    description  : 'What to sort results by.',
    enumValues   : [
        'created',
        'updated',
        'comments'
    ]
};

var paramDirection = {
    name         : 'direction',
    required     : false,
    defaultValue : 'desc',
    type         : 'enum',
    location     : 'query',
    description  : 'The direction of the sort.',
    enumValues   : [
        'desc',
        'asc'
    ]
};

var paramSince = {
    name         : 'since',
    required     : false,
    type         : 'string',
    location     : 'query',
    description  : 'Only issues updated at or after this time are returned. This is a timestamp in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ`.'
};

var paramOwner = {
    name        : 'owner',
    required    : true,
    type        : 'string',
    location    : 'uri',
    description : 'The owner of the repo.'
};

var paramRepo = {
    name        : 'repo',
    required    : true,
    type        : 'string',
    location    : 'uri',
    description : 'The name of the repo.'
};

var paramMilestone = {
    name         : 'milestone',
    required     : false,
    defaultValue : '*',
    type         : 'mixed',
    location     : 'query',
    description  : 'If an integer is passed, it should refer to a milestone number. If the string `*` is passed, issues with any milestone are accepted. If the string `none` is passed, issues without milestones are returned.'
};

var paramAssignee = {
    name         : 'assignee',
    required     : false,
    defaultValue : '*',
    type         : 'string',
    location     : 'query',
    description  : 'Can be the name of a user. Pass in `none` for issues with no assigned user, and `*` for issues assigned to any user.'
};

var paramNumber = {
    name        : 'number',
    required    : true,
    type        : 'integer',
    location    : 'uri',
    description : 'The issue number.'
};

var paramOrg = {
    name        : 'org',
    required    : true,
    type        : 'string',
    location    : 'uri',
    description : 'The name of the organization.'
};

module.exports = {
    name    : 'Issues',
    methods : [
        {
            name     : 'List issues (all)',
            synopsis : 'List all issues across all the authenticated user’s visible repositories including owned repositories, member repositories, and organization repositories.',
            method   : 'GET',
            uri      : '/issues',
            oauth    : true,
            params   : [
                paramFilter,
                paramState,
                paramLabels,
                paramSort,
                paramDirection,
                paramSince
            ]
        },
        {
            name     : 'List issues (mine)',
            synopsis : 'List all issues across owned and member repositories for the authenticated user.',
            method   : 'GET',
            uri      : '/user/issues',
            oauth    : true,
            params   : [
                paramFilter,
                paramState,
                paramLabels,
                paramSort,
                paramDirection,
                paramSince
            ]
        },
        {
            name     : 'List issues (org)',
            synopsis : 'List all issues for a given organization for the authenticated user.',
            method   : 'GET',
            uri      : '/orgs/:org/issues',
            oauth    : true,
            params   : [
                paramOrg,
                paramFilter,
                paramState,
                paramLabels,
                paramSort,
                paramDirection,
                paramSince
            ]
        },
        {
            name     : 'List issues for a repository',
            synopsis : 'List all issues for a given repository.',
            method   : 'GET',
            uri      : '/repos/:owner/:repo/issues',
            oauth    : false,
            params   : [
                paramOwner,
                paramRepo,
                paramMilestone,
                paramState,
                paramAssignee,
                paramLabels,
                paramSort,
                paramDirection,
                paramSince
            ]
        },
        {
            name     : 'Get a single issue',
            synopsis : 'Get a single issue from a given repository. **Note**: Every pull request is an issue, but not every issue is a pull request. If the issue is not a pull request, the response omits the `pull_request` attribute.',
            method   : 'GET',
            uri      : '/repos/:owner/:repo/issues/:number',
            oauth    : false,
            params   : [
                paramOwner,
                paramRepo,
                paramNumber
            ]
        },
        {
            name     : 'Create an issue',
            synopsis : 'Any user with pull access to a repository can create an issue.',
            method   : 'POST',
            uri      : '/repos/:owner/:repo/issues',
            oauth    : true,
            params   : [
                paramOwner,
                paramRepo,
                {
                    name         : 'title',
                    required     : true,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The title of the issue.'
                },
                {
                    name         : 'body',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The contents of the issue.'
                },
                {
                    name         : 'assignee',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'Login for the user that this issue should be assigned to. *NOTE: Only users with push access can set the assignee for new issues. The assignee is silently dropped otherwise.*'
                },
                {
                    name         : 'milestone',
                    required     : false,
                    type         : 'integer',
                    location     : 'body',
                    description  : 'Milestone to associate this issue with. *NOTE: Only users with push access can set the milestone for new issues. The milestone is silently dropped otherwise.*'
                },
                {
                    name         : 'labels',
                    required     : false,
                    type         : 'array',
                    location     : 'body',
                    description  : 'Labels to associate with this issue. This should be an `array` of `strings`. *NOTE: Only users with push access can set labels for new issues. Labels are silently dropped otherwise.*'
                }
            ]
        },
        {
            name     : 'Edit an issue',
            synopsis : 'Issue owners and users with push access can edit an issue.',
            method   : 'PATCH',
            uri      : '/repos/:owner/:repo/issues/:number',
            oauth    : true,
            params   : [
                paramOwner,
                paramRepo,
                paramNumber,
                {
                    name         : 'title',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The title of the issue.'
                },
                {
                    name         : 'body',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The contents of the issue.'
                },
                {
                    name         : 'assignee',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'Login for the user that this issue should be assigned to.'
                },
                {
                    name         : 'state',
                    required     : false,
                    type         : 'enum',
                    location     : 'body',
                    description  : 'State of the issue.',
                    enumValues   : [
                        'open',
                        'closed'
                    ]
                },
                {
                    name         : 'milestone',
                    required     : false,
                    type         : 'integer',
                    location     : 'body',
                    description  : 'Milestone to associate this issue with. *NOTE: Only users with push access can set the milestone for issues. The milestone is silently dropped otherwise.*'
                },
                {
                    name         : 'labels',
                    required     : false,
                    type         : 'array',
                    location     : 'body',
                    description  : 'Labels to associate with this issue. This should be an `array` of `strings`. Pass one or more Labels to *replace* the set of Labels on this Issue. Send an empty array (`[]`) to clear all Labels from the Issue. *NOTE: Only users with push access can set labels for issues. Labels are silently dropped otherwise.*'
                }
            ]
        }
    ]
};
<commit_msg>Refs #64 - Add mentioned and creator parameters to list issues for repo endpoint<commit_after>'use strict';

var paramFilter = {
    name         : 'filter',
    required     : false,
    defaultValue : 'assigned',
    type         : 'enum',
    location     : 'query',
    description  : 'Indicates which sorts of issues to return.',
    enumValues   : [
        'assigned',
        'created',
        'mentioned',
        'subscribed',
        'all'
    ]
};

var paramState = {
    name         : 'state',
    required     : false,
    defaultValue : 'open',
    type         : 'enum',
    location     : 'query',
    description  : 'Indicates the state of the issues to return.',
    enumValues   : [
        'open',
        'closed',
        'all'
    ]
};

var paramLabels = {
    name         : 'labels',
    required     : false,
    type         : 'string',
    location     : 'query',
    description  : 'A list of comma separated label names. Example: `bug,ui,@high`'
};

var paramSort = {
    name         : 'sort',
    required     : false,
    defaultValue : 'created',
    type         : 'enum',
    location     : 'query',
    description  : 'What to sort results by.',
    enumValues   : [
        'created',
        'updated',
        'comments'
    ]
};

var paramDirection = {
    name         : 'direction',
    required     : false,
    defaultValue : 'desc',
    type         : 'enum',
    location     : 'query',
    description  : 'The direction of the sort.',
    enumValues   : [
        'desc',
        'asc'
    ]
};

var paramSince = {
    name         : 'since',
    required     : false,
    type         : 'string',
    location     : 'query',
    description  : 'Only issues updated at or after this time are returned. This is a timestamp in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ`.'
};

var paramOwner = {
    name        : 'owner',
    required    : true,
    type        : 'string',
    location    : 'uri',
    description : 'The owner of the repo.'
};

var paramRepo = {
    name        : 'repo',
    required    : true,
    type        : 'string',
    location    : 'uri',
    description : 'The name of the repo.'
};

var paramMilestone = {
    name         : 'milestone',
    required     : false,
    defaultValue : '*',
    type         : 'mixed',
    location     : 'query',
    description  : 'If an integer is passed, it should refer to a milestone number. If the string `*` is passed, issues with any milestone are accepted. If the string `none` is passed, issues without milestones are returned.'
};

var paramAssignee = {
    name         : 'assignee',
    required     : false,
    defaultValue : '*',
    type         : 'string',
    location     : 'query',
    description  : 'Can be the name of a user. Pass in `none` for issues with no assigned user, and `*` for issues assigned to any user.'
};

var paramNumber = {
    name        : 'number',
    required    : true,
    type        : 'integer',
    location    : 'uri',
    description : 'The issue number.'
};

var paramOrg = {
    name        : 'org',
    required    : true,
    type        : 'string',
    location    : 'uri',
    description : 'The name of the organization.'
};

module.exports = {
    name    : 'Issues',
    methods : [
        {
            name     : 'List issues (all)',
            synopsis : 'List all issues across all the authenticated user’s visible repositories including owned repositories, member repositories, and organization repositories.',
            method   : 'GET',
            uri      : '/issues',
            oauth    : true,
            params   : [
                paramFilter,
                paramState,
                paramLabels,
                paramSort,
                paramDirection,
                paramSince
            ]
        },
        {
            name     : 'List issues (mine)',
            synopsis : 'List all issues across owned and member repositories for the authenticated user.',
            method   : 'GET',
            uri      : '/user/issues',
            oauth    : true,
            params   : [
                paramFilter,
                paramState,
                paramLabels,
                paramSort,
                paramDirection,
                paramSince
            ]
        },
        {
            name     : 'List issues (org)',
            synopsis : 'List all issues for a given organization for the authenticated user.',
            method   : 'GET',
            uri      : '/orgs/:org/issues',
            oauth    : true,
            params   : [
                paramOrg,
                paramFilter,
                paramState,
                paramLabels,
                paramSort,
                paramDirection,
                paramSince
            ]
        },
        {
            name     : 'List issues for a repository',
            synopsis : 'List all issues for a given repository.',
            method   : 'GET',
            uri      : '/repos/:owner/:repo/issues',
            oauth    : false,
            params   : [
                paramOwner,
                paramRepo,
                paramMilestone,
                paramState,
                paramAssignee,
                {
                    name         : 'creator',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The user that created the issue.'
                },
                {
                    name         : 'mentioned',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The user that\'s mentioned in the issue.'
                },
                paramLabels,
                paramSort,
                paramDirection,
                paramSince
            ]
        },
        {
            name     : 'Get a single issue',
            synopsis : 'Get a single issue from a given repository. **Note**: Every pull request is an issue, but not every issue is a pull request. If the issue is not a pull request, the response omits the `pull_request` attribute.',
            method   : 'GET',
            uri      : '/repos/:owner/:repo/issues/:number',
            oauth    : false,
            params   : [
                paramOwner,
                paramRepo,
                paramNumber
            ]
        },
        {
            name     : 'Create an issue',
            synopsis : 'Any user with pull access to a repository can create an issue.',
            method   : 'POST',
            uri      : '/repos/:owner/:repo/issues',
            oauth    : true,
            params   : [
                paramOwner,
                paramRepo,
                {
                    name         : 'title',
                    required     : true,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The title of the issue.'
                },
                {
                    name         : 'body',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The contents of the issue.'
                },
                {
                    name         : 'assignee',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'Login for the user that this issue should be assigned to. *NOTE: Only users with push access can set the assignee for new issues. The assignee is silently dropped otherwise.*'
                },
                {
                    name         : 'milestone',
                    required     : false,
                    type         : 'integer',
                    location     : 'body',
                    description  : 'Milestone to associate this issue with. *NOTE: Only users with push access can set the milestone for new issues. The milestone is silently dropped otherwise.*'
                },
                {
                    name         : 'labels',
                    required     : false,
                    type         : 'array',
                    location     : 'body',
                    description  : 'Labels to associate with this issue. This should be an `array` of `strings`. *NOTE: Only users with push access can set labels for new issues. Labels are silently dropped otherwise.*'
                }
            ]
        },
        {
            name     : 'Edit an issue',
            synopsis : 'Issue owners and users with push access can edit an issue.',
            method   : 'PATCH',
            uri      : '/repos/:owner/:repo/issues/:number',
            oauth    : true,
            params   : [
                paramOwner,
                paramRepo,
                paramNumber,
                {
                    name         : 'title',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The title of the issue.'
                },
                {
                    name         : 'body',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'The contents of the issue.'
                },
                {
                    name         : 'assignee',
                    required     : false,
                    type         : 'string',
                    location     : 'body',
                    description  : 'Login for the user that this issue should be assigned to.'
                },
                {
                    name         : 'state',
                    required     : false,
                    type         : 'enum',
                    location     : 'body',
                    description  : 'State of the issue.',
                    enumValues   : [
                        'open',
                        'closed'
                    ]
                },
                {
                    name         : 'milestone',
                    required     : false,
                    type         : 'integer',
                    location     : 'body',
                    description  : 'Milestone to associate this issue with. *NOTE: Only users with push access can set the milestone for issues. The milestone is silently dropped otherwise.*'
                },
                {
                    name         : 'labels',
                    required     : false,
                    type         : 'array',
                    location     : 'body',
                    description  : 'Labels to associate with this issue. This should be an `array` of `strings`. Pass one or more Labels to *replace* the set of Labels on this Issue. Send an empty array (`[]`) to clear all Labels from the Issue. *NOTE: Only users with push access can set labels for issues. Labels are silently dropped otherwise.*'
                }
            ]
        }
    ]
};
<|endoftext|>


<commit_before>var _ = require('lodash'),
    config = require('nconf'),
    rest = require('rest'),
    mime = require('rest/interceptor/mime'),
    client = rest.wrap(mime, { mime: 'application/json' });

exports.receiveEvent = function (req, res) {
  var endpoint, body, transition, shortMessage, configProps, projectKey,
      ev = req.body;

  var hasTransition = _.has(ev, 'transition');

  if (!hasTransition && _.has(ev, 'changelog')) { // Newer jira versions
    _.each(ev.changelog.items, function (change) {
      if (change.field == 'status' && change.fromString) {
        ev.transition = { from_status: change.fromString, transitionName: change.toString }
        hasTransition = true;
      }
    });
  }

  projectKey = ev.issue.key.split('-')[0];

  configProps = {
    slackUrl: projectKey + ':SLACK_URL',
    jiraDomain: projectKey + ':JIRA_DOMAIN'
  };

  if (!config.get(configProps.slackUrl) || !config.get(configProps.jiraDomain))
    return;

  endpoint = config.get(configProps.slackUrl);

  if(_.has(ev, 'issue'))
    var issueUrl = 'https://' + config.get(configProps.jiraDomain) + '/browse/' + ev.issue.key;

  if (_.has(ev, 'webhookEvent') && ev.webhookEvent == 'jira:issue_created') {
    /* jshint camelcase:false */
    transition = {
      issue: ev.issue.key,
      issueUrl: issueUrl,
      description: ev.issue.fields.summary,
      who: ev.user.displayName
    };

    transition.assignee = (ev.issue.fields.assignee) ?
        ev.issue.fields.assignee.displayName : 'Unassigned';

    shortMessage = _.template(
        ':zap: *<%= t.who %>* created <<%= t.issueUrl %>|<%= t.issue %>>',
        { t: transition }
    );

    body = {
      attachments: [
        {
          "mrkdwn_in": ["pretext", "fallback"],
          fallback: shortMessage,
          pretext: shortMessage,
          color: '#C0C0C0',
          fields: [
            {
              title: 'Current Assignee',
              value: _.template(
                  '<%= t.assignee %>',
                  { t: transition }
              ),
              short: true
            },
            {
              title: 'Summary',
              value: _.template(
                  '<%= t.description %>',
                  { t: transition }
              ),
              short: false
            }
          ]
        }
      ]
    };

    client({
      path: endpoint,
      entity: body
    }).then(function (response) {
      console.log('POST -- ' + endpoint);
      console.log(response.status);
    });
  }

  if (hasTransition) {
    /* jshint camelcase:false */
    transition = {
      issue: ev.issue.key,
      issueUrl: issueUrl,
      description: ev.issue.fields.summary,
      fromStatus: ev.transition.from_status,
      toStatus: ev.transition.transitionName,
      who: ev.user.displayName
    };

    transition.assignee = (ev.issue.fields.assignee) ?
        ev.issue.fields.assignee.displayName : 'Unassigned';

    shortMessage = _.template(
        '*<%= t.who %>* moved <<%= t.issueUrl %>|<%= t.issue %>>',
        { t: transition }
    );

    body = {
      attachments: [
        {
          "mrkdwn_in": ["pretext", "fallback"],
          fallback: shortMessage,
          pretext: shortMessage,
          color: '#C0C0C0',
          fields: [
            {
              title: 'Transition',
              value: _.template(
                  '<%= t.fromStatus %> → <%= t.toStatus %>',
                  { t: transition }
              ),
              short: true
            },
            {
              title: 'Current Assignee',
              value: _.template(
                  '<%= t.assignee %>',
                  { t: transition }
              ),
              short: true
            },
            {
              title: 'Summary',
              value: _.template(
                  '<%= t.description %>',
                  { t: transition }
              ),
              short: false
            }
          ]
        }
      ]
    };

    client({
      path: endpoint,
      entity: body
    }).then(function (response) {
      console.log('POST -- ' + endpoint);
      console.log(response.status);
    });
  }

  if (_.has(ev, 'comment') && !_.isNull(ev.comment)) {
    var isEdit = ev.comment.created != ev.comment.updated;

    var action = isEdit ? 'updated a comment' : 'commented';

    var commentUrl = issueUrl + '?focusedCommentId=' + ev.comment.id + '&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-' + ev.comment.id;

    var shortMessage = _.template(
        '*<%= c.author.displayName %>* <<%= commentUrl %>|<%= action %>> on issue <<%= issueUrl %>|<%= t.issue.id%> - <%= issueSummary %>>',
        { c: ev.comment, t: ev, issueUrl: issueUrl, action: action, commentUrl: commentUrl,
          issueSummary: ev.issue.fields.summary.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<')
        }
    );

    var body = {
      attachments: [
        {
          "mrkdwn_in": ["pretext", "fallback", "text"],
          fallback: shortMessage,
          pretext: shortMessage,
          color: '#C0C0C0',
          text: ev.comment.body.replace(/!([^\s]+)!/g, "$1").replace(/\{\{(.+)\}\}/g, "`$1`")
        }
      ]
    };

    client({
      path: endpoint,
      entity: body
    }).then(function (response) {
      console.log('POST -- ' + endpoint);
      console.log(response.status);
    });
  }

  res.json(200);
};
<commit_msg>Added Priority field to issue created notification<commit_after>var _ = require('lodash'),
    config = require('nconf'),
    rest = require('rest'),
    mime = require('rest/interceptor/mime'),
    client = rest.wrap(mime, { mime: 'application/json' });

exports.receiveEvent = function (req, res) {
  var endpoint, body, transition, shortMessage, configProps, projectKey,
      ev = req.body;

  var hasTransition = _.has(ev, 'transition');

  if (!hasTransition && _.has(ev, 'changelog')) { // Newer jira versions
    _.each(ev.changelog.items, function (change) {
      if (change.field == 'status' && change.fromString) {
        ev.transition = { from_status: change.fromString, transitionName: change.toString }
        hasTransition = true;
      }
    });
  }

  projectKey = ev.issue.key.split('-')[0];

  configProps = {
    slackUrl: projectKey + ':SLACK_URL',
    jiraDomain: projectKey + ':JIRA_DOMAIN'
  };

  if (!config.get(configProps.slackUrl) || !config.get(configProps.jiraDomain))
    return;

  endpoint = config.get(configProps.slackUrl);

  if(_.has(ev, 'issue'))
    var issueUrl = 'https://' + config.get(configProps.jiraDomain) + '/browse/' + ev.issue.key;

  if (_.has(ev, 'webhookEvent') && ev.webhookEvent == 'jira:issue_created') {
    /* jshint camelcase:false */
    transition = {
      issue: ev.issue.key,
      issueUrl: issueUrl,
      description: ev.issue.fields.summary,
      who: ev.user.displayName,
      priority: ev.issue.fields.priority.name,
      icon: ''
    };

    transition.assignee = (ev.issue.fields.assignee) ?
        ev.issue.fields.assignee.displayName : 'Unassigned';

    var issueTypes = {
      'Task': ':memo:',
      'New Feature': ':sparkles:',
      'Bug': ':boom:',
      'Improvement': ':wrench:',
      'Epic': ':book:',
      'Story': ':bulb:'
    };

    if(typeof issueTypes[ev.issue.fields.issuetype.name] !== 'undefined') {
      transition.icon = issueTypes[ev.issue.fields.issuetype.name] + ' ';
    }

    shortMessage = _.template(
        '<%= t.icon %>*<%= t.who %>* created <<%= t.issueUrl %>|<%= t.issue %>>',
        { t: transition }
    );

    body = {
      attachments: [
        {
          "mrkdwn_in": ["pretext", "fallback"],
          fallback: shortMessage,
          pretext: shortMessage,
          color: '#42B854',
          fields: [
            {
              title: 'Current Assignee',
              value: _.template(
                  '<%= t.assignee %>',
                  { t: transition }
              ),
              short: true
            },
            {
              title: 'Priority',
              value: _.template(
                  '<%= t.priority %>',
                  { t: transition }
              ),
              short: true
            },
            {
              title: 'Summary',
              value: _.template(
                  '<%= t.description %>',
                  { t: transition }
              ),
              short: false
            }
          ]
        }
      ]
    };

    client({
      path: endpoint,
      entity: body
    }).then(function (response) {
      console.log('POST -- ' + endpoint);
      console.log(response.status);
    });
  }

  if (hasTransition) {
    /* jshint camelcase:false */
    transition = {
      issue: ev.issue.key,
      issueUrl: issueUrl,
      description: ev.issue.fields.summary,
      fromStatus: ev.transition.from_status,
      toStatus: ev.transition.transitionName,
      who: ev.user.displayName
    };

    transition.assignee = (ev.issue.fields.assignee) ?
        ev.issue.fields.assignee.displayName : 'Unassigned';

    shortMessage = _.template(
        '*<%= t.who %>* moved <<%= t.issueUrl %>|<%= t.issue %>>',
        { t: transition }
    );

    body = {
      attachments: [
        {
          "mrkdwn_in": ["pretext", "fallback"],
          fallback: shortMessage,
          pretext: shortMessage,
          color: '#C0C0C0',
          fields: [
            {
              title: 'Transition',
              value: _.template(
                  '<%= t.fromStatus %> → <%= t.toStatus %>',
                  { t: transition }
              ),
              short: true
            },
            {
              title: 'Current Assignee',
              value: _.template(
                  '<%= t.assignee %>',
                  { t: transition }
              ),
              short: true
            },
            {
              title: 'Summary',
              value: _.template(
                  '<%= t.description %>',
                  { t: transition }
              ),
              short: false
            }
          ]
        }
      ]
    };

    client({
      path: endpoint,
      entity: body
    }).then(function (response) {
      console.log('POST -- ' + endpoint);
      console.log(response.status);
    });
  }

  if (_.has(ev, 'comment') && !_.isNull(ev.comment)) {
    var isEdit = ev.comment.created != ev.comment.updated;

    var action = isEdit ? 'updated a comment' : 'commented';

    var commentUrl = issueUrl + '?focusedCommentId=' + ev.comment.id + '&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-' + ev.comment.id;

    var shortMessage = _.template(
        '*<%= c.author.displayName %>* <<%= commentUrl %>|<%= action %>> on issue <<%= issueUrl %>|#<%= t.issue.key %> - <%= issueSummary %>>',
        { c: ev.comment, t: ev, issueUrl: issueUrl, action: action, commentUrl: commentUrl,
          issueSummary: ev.issue.fields.summary.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<')
        }
    );

    var body = {
      attachments: [
        {
          "mrkdwn_in": ["pretext", "fallback", "text"],
          fallback: shortMessage,
          pretext: shortMessage,
          color: '#C0C0C0',
          text: ev.comment.body.replace(/!([^\s]+)!/g, "$1").replace(/\{\{(.+)\}\}/g, "`$1`")
        }
      ]
    };

    client({
      path: endpoint,
      entity: body
    }).then(function (response) {
      console.log('POST -- ' + endpoint);
      console.log(response.status);
    });
  }

  res.json(200);
};
<|endoftext|>


<commit_before>// ==========================================================================
//                      DG.UndoHistory
//
//  The controller for managing undo/redo.
//
//  Based on concepts from Fathom, Ivy (https://github.com/concord-consortium/building-models/blob/master/src/code/utils/undo-redo.coffee),
//    and undo.js (https://github.com/jzaefferer/undo/blob/master/undo.js)
//
//  Author:   Aaron Unger
//
//  Copyright (c) 2015 by The Concord Consortium, 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

  @extends SC.Object
*/
DG.UndoHistory = SC.Object.create((function() {
/** @scope DG.UndoHistory.prototype */
  return {
    /** @private
      A list of commands available to be undone. The last item in the array is the next item
      to be undone. This makes it easy to use pop() when undoing things and push() to add new commands on.
     */
    _undoStack: [],

    /** @private
      A list of commands available to be redone. The last item in the array is the next item
      to be redone. This makes it easy to use pop() when redoing things and push() to add new commands on.
     */
    _redoStack: [],

    /** @private
      A flag so that we can know if calls to documentWasChanged happened inside of a command function or not.
     */
    _executeInProgress: false,

    /**
      Registers a command which can be undone and redone at a later point. The command is immediately executed,
      and then added to the stack for later.
      @param    {DG.Command}  command -- An instance of DG.Command, which provides methods for doing, undoing and redoing an action.
     */
    execute: function(command) {
      try {
        this._wrapAndRun(command, command.execute); // Just let exceptions bubble up, but clear the stacks...
      } finally {
        this._clearUndo();
        this._clearRedo();
      }

      if (command.isUndoable) {
        this._undoStack.push(command);
        // Since we're not using set/get to access the stacks, notify changes manually.
        this.notifyPropertyChange('_undoStack');
      } else {
        // We've hit an not-undoable command, so clear our undo stack
        this._clearUndo();
      }
      // Clear the redo stack every time we add a new command. We *don't* support applying changes
      // from a different change tree to the current tree.
      this._clearRedo();
    },

    /**
     *  A flag indicating whether we have any commands to undo.
     *  @property {Boolean}
     */
    canUndo: function() {
      return this._undoStack.length > 0;
    }.property('_undoStack'),

    /**
     *  The next command which will be undone.
     *  @property {DG.Command}
     */
    nextUndoCommand: function() {
      if (this._undoStack.length === 0) {
        return null;
      }
      return this._undoStack[this._undoStack.length-1];
    }.property('_undoStack'),

    /**
      Undoes the most recently executed command and adds that command to the redo stack.
     */
    undo: function() {
      // You can get here directly from a keyboard shortcut, so rather than check if we
      // have anything to undo outside of this function, we'll just double-check here.
      if (this._undoStack.length === 0) {
        return;
      }
      var command = this._undoStack.pop();
      try {
        this._wrapAndRun(command, command.undo);
      } catch (e) {
        // Something went wrong. We shouldn't ever reach here, because if we do, it's because of a programming error.
        // BUT, if it does happen, display an error dialog to the user, and then clear the undo/redo stacks.
        // Odds are pretty high that the entire app is going to be messed up too, but there's not much we can do about that.
        DG.Debug.logError("Exception occurred while undoing: " + e.message);
        DG.AlertPane.error({
          localize: true,
          message: 'DG.Undo.exceptionOccurred',
          buttons: [
            {title: "OK", action: function() { this._clearUndo(); this._clearRedo(); }.bind(this) }
          ]
        });
      }
      this._redoStack.push(command);

      // Since we're not using set/get to access the stacks, notify changes manually.
      this.notifyPropertyChange('_undoStack');
      this.notifyPropertyChange('_redoStack');
    },

    /**
     *  A flag indicating whether we have any commands to redo.
     *  @property {Boolean}
     */
    canRedo: function() {
      return this._redoStack.length > 0;
    }.property('_redoStack'),

    /**
     *  The next command which will be redone.
     *  @property {DG.Command}
     */
    nextRedoCommand: function() {
      if (this._redoStack.length === 0) {
        return null;
      }
      return this._redoStack[this._redoStack.length-1];
    }.property('_redoStack'),

    /**
      Redoes the most recently undone command and adds that command to the undo stack.
     */
    redo: function() {
      // You can get here directly from a keyboard shortcut, so rather than check if we
      // have anything to redo outside of this function, we'll just double-check here.
      if (this._redoStack.length === 0) {
        return;
      }
      var command = this._redoStack.pop();
      try {
        this._wrapAndRun(command, command.redo);
      } catch (e) {
        // Something went wrong. We shouldn't ever reach here, because if we do, it's because of a programming error.
        // BUT, if it does happen, display an error dialog to the user, and then clear the undo/redo stacks.
        // Odds are pretty high that the entire app is going to be messed up too, but there's not much we can do about that.
        DG.Debug.logError("Exception occurred while redoing: " + e.message);
        DG.AlertPane.error({
          localize: true,
          message: 'DG.Redo.exceptionOccurred',
          buttons: [
            {title: "OK", action: function() { this._clearUndo(); this._clearRedo(); }.bind(this) }
          ]
        });
      }
      this._undoStack.push(command);

      // Since we're not using set/get to access the stacks, notify changes manually.
      this.notifyPropertyChange('_undoStack');
      this.notifyPropertyChange('_redoStack');
    },

    /**
      This method is called automatically by DG.dirtyCurrentDocument. If a document is dirtied
      as part of a command execution, then we ignore it. Otherwise, we clear the undo and redo stacks
      since a document-changing action took place which can't be undone.
     */
    documentWasChanged: function() {
      if (this._executeInProgress) { return; } // This is fine, we're doing it as part of a Command action

      // Otherwise, this wasn't part of a command, so this change is not able to be undone.
      // Clear the stacks
      this._clearUndo();
      this._clearRedo();
    },

    /** @private
      Helper method to run a function in a certain context. All calls to documentWasChanged will be ignored during its execution.
      @param    {DG.Command}   cmd -- An instance of DG.Command, which is used as the context for the function execution.
      @param    {Function}    func -- A function to be executed.
     */
    _wrapAndRun: function(cmd, func) {
      this._executeInProgress = true;
      // We'll catch exceptions higher up, so only clean up after ourselves here...
      try {
        func.call(cmd);
      } finally {
        this._executeInProgress = false;
      }
    },

    /** @private
      Helper function for clearing the undo stack.
     */
    _clearUndo: function() {
      this._undoStack.length = 0;
      this.notifyPropertyChange('_undoStack');
    },

    /** @private
      Helper function for clearing the redo stack.
     */
    _clearRedo: function() {
      this._redoStack.length = 0;
      this.notifyPropertyChange('_redoStack');
    }

  }; // return from function closure
})()); // function closure
<commit_msg>Undo/Redo: Fix error in handling exceptions in execute()<commit_after>// ==========================================================================
//                      DG.UndoHistory
//
//  The controller for managing undo/redo.
//
//  Based on concepts from Fathom, Ivy (https://github.com/concord-consortium/building-models/blob/master/src/code/utils/undo-redo.coffee),
//    and undo.js (https://github.com/jzaefferer/undo/blob/master/undo.js)
//
//  Author:   Aaron Unger
//
//  Copyright (c) 2015 by The Concord Consortium, 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

  @extends SC.Object
*/
DG.UndoHistory = SC.Object.create((function() {
/** @scope DG.UndoHistory.prototype */
  return {
    /** @private
      A list of commands available to be undone. The last item in the array is the next item
      to be undone. This makes it easy to use pop() when undoing things and push() to add new commands on.
     */
    _undoStack: [],

    /** @private
      A list of commands available to be redone. The last item in the array is the next item
      to be redone. This makes it easy to use pop() when redoing things and push() to add new commands on.
     */
    _redoStack: [],

    /** @private
      A flag so that we can know if calls to documentWasChanged happened inside of a command function or not.
     */
    _executeInProgress: false,

    /**
      Registers a command which can be undone and redone at a later point. The command is immediately executed,
      and then added to the stack for later.
      @param    {DG.Command}  command -- An instance of DG.Command, which provides methods for doing, undoing and redoing an action.
     */
    execute: function(command) {
      try {
        this._wrapAndRun(command, command.execute);
      } catch(e) {
        // Just let exceptions bubble up, but clear the stacks...
        this._clearUndo();
        this._clearRedo();
        throw e;
      }

      if (command.isUndoable) {
        this._undoStack.push(command);
        // Since we're not using set/get to access the stacks, notify changes manually.
        this.notifyPropertyChange('_undoStack');
      } else {
        // We've hit an not-undoable command, so clear our undo stack
        this._clearUndo();
      }
      // Clear the redo stack every time we add a new command. We *don't* support applying changes
      // from a different change tree to the current tree.
      this._clearRedo();
    },

    /**
     *  A flag indicating whether we have any commands to undo.
     *  @property {Boolean}
     */
    canUndo: function() {
      return this._undoStack.length > 0;
    }.property('_undoStack'),

    /**
     *  The next command which will be undone.
     *  @property {DG.Command}
     */
    nextUndoCommand: function() {
      if (this._undoStack.length === 0) {
        return null;
      }
      return this._undoStack[this._undoStack.length-1];
    }.property('_undoStack'),

    /**
      Undoes the most recently executed command and adds that command to the redo stack.
     */
    undo: function() {
      // You can get here directly from a keyboard shortcut, so rather than check if we
      // have anything to undo outside of this function, we'll just double-check here.
      if (this._undoStack.length === 0) {
        return;
      }
      var command = this._undoStack.pop();
      try {
        this._wrapAndRun(command, command.undo);
      } catch (e) {
        // Something went wrong. We shouldn't ever reach here, because if we do, it's because of a programming error.
        // BUT, if it does happen, display an error dialog to the user, and then clear the undo/redo stacks.
        // Odds are pretty high that the entire app is going to be messed up too, but there's not much we can do about that.
        DG.Debug.logError("Exception occurred while undoing: " + e.message);
        DG.AlertPane.error({
          localize: true,
          message: 'DG.Undo.exceptionOccurred',
          buttons: [
            {title: "OK", action: function() { this._clearUndo(); this._clearRedo(); }.bind(this) }
          ]
        });
      }
      this._redoStack.push(command);

      // Since we're not using set/get to access the stacks, notify changes manually.
      this.notifyPropertyChange('_undoStack');
      this.notifyPropertyChange('_redoStack');
    },

    /**
     *  A flag indicating whether we have any commands to redo.
     *  @property {Boolean}
     */
    canRedo: function() {
      return this._redoStack.length > 0;
    }.property('_redoStack'),

    /**
     *  The next command which will be redone.
     *  @property {DG.Command}
     */
    nextRedoCommand: function() {
      if (this._redoStack.length === 0) {
        return null;
      }
      return this._redoStack[this._redoStack.length-1];
    }.property('_redoStack'),

    /**
      Redoes the most recently undone command and adds that command to the undo stack.
     */
    redo: function() {
      // You can get here directly from a keyboard shortcut, so rather than check if we
      // have anything to redo outside of this function, we'll just double-check here.
      if (this._redoStack.length === 0) {
        return;
      }
      var command = this._redoStack.pop();
      try {
        this._wrapAndRun(command, command.redo);
      } catch (e) {
        // Something went wrong. We shouldn't ever reach here, because if we do, it's because of a programming error.
        // BUT, if it does happen, display an error dialog to the user, and then clear the undo/redo stacks.
        // Odds are pretty high that the entire app is going to be messed up too, but there's not much we can do about that.
        DG.Debug.logError("Exception occurred while redoing: " + e.message);
        DG.AlertPane.error({
          localize: true,
          message: 'DG.Redo.exceptionOccurred',
          buttons: [
            {title: "OK", action: function() { this._clearUndo(); this._clearRedo(); }.bind(this) }
          ]
        });
      }
      this._undoStack.push(command);

      // Since we're not using set/get to access the stacks, notify changes manually.
      this.notifyPropertyChange('_undoStack');
      this.notifyPropertyChange('_redoStack');
    },

    /**
      This method is called automatically by DG.dirtyCurrentDocument. If a document is dirtied
      as part of a command execution, then we ignore it. Otherwise, we clear the undo and redo stacks
      since a document-changing action took place which can't be undone.
     */
    documentWasChanged: function() {
      if (this._executeInProgress) { return; } // This is fine, we're doing it as part of a Command action

      // Otherwise, this wasn't part of a command, so this change is not able to be undone.
      // Clear the stacks
      this._clearUndo();
      this._clearRedo();
    },

    /** @private
      Helper method to run a function in a certain context. All calls to documentWasChanged will be ignored during its execution.
      @param    {DG.Command}   cmd -- An instance of DG.Command, which is used as the context for the function execution.
      @param    {Function}    func -- A function to be executed.
     */
    _wrapAndRun: function(cmd, func) {
      this._executeInProgress = true;
      // We'll catch exceptions higher up, so only clean up after ourselves here...
      try {
        func.call(cmd);
      } finally {
        this._executeInProgress = false;
      }
    },

    /** @private
      Helper function for clearing the undo stack.
     */
    _clearUndo: function() {
      this._undoStack.length = 0;
      this.notifyPropertyChange('_undoStack');
    },

    /** @private
      Helper function for clearing the redo stack.
     */
    _clearRedo: function() {
      this._redoStack.length = 0;
      this.notifyPropertyChange('_redoStack');
    }

  }; // return from function closure
})()); // function closure
<|endoftext|>


<commit_before>// ==========================================================================
// Project:   Greenhouse
// Copyright: ©2009 Mike Ball
// ==========================================================================
/*globals Greenhouse */
/*jslint evil: true */

/** @mixin
  @extends Greenhouse
  @author Mike Ball
  @author Evin Grano
  @version RC1
  @since RC1
*/
Greenhouse.mixin( /** @scope Greenhouse */{
  inspectorClosed: SC.State.create({

    parallelStatechart: 'inspector',

    // ..........................................................
    // Events
    //
    openInspector: function(){
      this.goState('openInspectorPicker');
    },

    toggleDockedInspector: function(){
      this.goState('dockedInspector');
    }
  }),

  openInspectorPicker: SC.State.create({
    parallelStatechart: 'inspector',

    enterState: function(){
      var ap = Greenhouse.appPage;
      var picker = ap.get('inspectorPicker'),
          button = ap.getPath('mainView.toolBar.inspector'),
          pickerContentView = ap.get('inspectorPickerContentView');

      pickerContentView.setIfChanged('nowShowing', 'Greenhouse.appPage.inspectorContentView');
      picker.popup(button, SC.PICKER_POINTER);
      picker.becomeFirstResponder();
    },
    exitState: function(){
      var ap = Greenhouse.appPage;
      var picker = ap.get('inspectorPicker'),
          pickerContentView = ap.get('inspectorPickerContentView');
      pickerContentView.setIfChanged('nowShowing', null);
      picker.remove();
    },

    // ..........................................................
    // Events
    //
    cancel: function(){
      this.goState('inspectorClosed');
    },

    floatInspector: function(){
      this.goState('inspectorPalette');
    },

    toggleDockedInspector: function(){
      this.goState('dockedInspector');
    }
  }),

  inspectorPalette: SC.State.create({
    parallelStatechart: 'inspector',

    enterState: function(){
      var ap = Greenhouse.appPage;
      var picker = ap.get('inspectorPicker'),
          pickerContentView = ap.get('inspectorPickerContentView');

      pickerContentView.setIfChanged('nowShowing', 'Greenhouse.appPage.inspectorContentView');
      picker.append();
      picker.set('isModal', NO);
      picker.set('isAnchored', NO);
      picker.$().toggleClass('sc-picker', NO);
      var content = ap.getPath('inspectorPickerContentView.content'),
          toolbar = ap.getPath('inspectorPickerContentView.toolbar');

      content.adjust('top', 28);
      toolbar.set('isVisible', YES);
    },
    exitState: function(){
      var ap = Greenhouse.appPage;
      var picker = ap.get('inspectorPicker'),
          pickerContentView = ap.get('inspectorPickerContentView');

      pickerContentView.setIfChanged('nowShowing', null);
      picker.set('isModal', YES);
      picker.set('isAnchored', YES);
      picker.remove();

      var content = ap.getPath('inspectorPickerContentView.content'),
          toolbar = ap.getPath('inspectorPickerContentView.toolbar');

      content.adjust('top', 0);
      toolbar.set('isVisible', NO);
    },

    // ..........................................................
    // Events
    //
    closeInspector: function(){
      this.goState('inspectorClosed');
    },

    toggleDockedInspector: function(){
      this.goState('dockedInspector');
    }
  }),

  dockedInspector: SC.State.create({
    parallelStatechart: 'inspector',

    enterState: function(){
      var iDock = Greenhouse.appPage.get('inspectorDockView');
      iDock.setIfChanged('nowShowing', 'Greenhouse.appPage.inspectorContentView');
    },
    exitState: function(){
      var iDock = Greenhouse.appPage.get('inspectorDockView');
      iDock.setIfChanged('nowShowing', null);
    },

    // ..........................................................
    // Events
    //
    toggleDockedInspector: function(){
      var libState = Greenhouse.get('libraryClosed').state();
      if (libState !== Greenhouse.get('dockedLibrary')) Greenhouse.sendEvent('undock');
      this.goState('inspectorClosed');
    }
  })
});<commit_msg>fixed a bug with dragging the inspector from the pickerpane<commit_after>// ==========================================================================
// Project:   Greenhouse
// Copyright: ©2009 Mike Ball
// ==========================================================================
/*globals Greenhouse */
/*jslint evil: true */

/** @mixin
  @extends Greenhouse
  @author Mike Ball
  @author Evin Grano
  @version RC1
  @since RC1
*/
Greenhouse.mixin( /** @scope Greenhouse */{
  inspectorClosed: SC.State.create({

    parallelStatechart: 'inspector',

    // ..........................................................
    // Events
    //
    openInspector: function(){
      this.goState('openInspectorPicker');
    },

    toggleDockedInspector: function(){
      this.goState('dockedInspector');
    }
  }),

  openInspectorPicker: SC.State.create({
    parallelStatechart: 'inspector',

    enterState: function(){
      var ap = Greenhouse.appPage;
      var picker = ap.get('inspectorPicker'),
          button = ap.getPath('mainView.toolBar.inspector'),
          pickerContentView = ap.get('inspectorPickerContentView');

      pickerContentView.setIfChanged('nowShowing', 'Greenhouse.appPage.inspectorContentView');
      picker.popup(button, SC.PICKER_POINTER);
      picker.becomeFirstResponder();
    },
    exitState: function(){
      var ap = Greenhouse.appPage;
      var picker = ap.get('inspectorPicker'),
          pickerContentView = ap.get('inspectorPickerContentView');
      pickerContentView.setIfChanged('nowShowing', null);
      picker.remove();
    },

    // ..........................................................
    // Events
    //
    cancel: function(){
      this.goState('inspectorClosed');
    },

    floatInspector: function(){
      this.goState('inspectorPalette');
    },

    toggleDockedInspector: function(){
      this.goState('dockedInspector');
    }
  }),

  inspectorPalette: SC.State.create({
    parallelStatechart: 'inspector',

    enterState: function(){
      var ap = Greenhouse.appPage;
      var picker = ap.get('inspectorPicker'),
          pickerContentView = ap.get('inspectorPickerContentView');

      pickerContentView.setIfChanged('nowShowing', 'Greenhouse.appPage.inspectorContentView');
      picker.append();
      picker.set('isModal', NO);
      picker.set('isAnchored', NO);
      picker.$().toggleClass('sc-picker', NO);
      var content = ap.getPath('inspectorContentView.content'),
          toolbar = ap.getPath('inspectorContentView.toolbar');

      content.adjust('top', 28);
      toolbar.set('isVisible', YES);
    },
    exitState: function(){
      var ap = Greenhouse.appPage;
      var picker = ap.get('inspectorPicker'),
          pickerContentView = ap.get('inspectorPickerContentView');

      pickerContentView.setIfChanged('nowShowing', null);
      picker.set('isModal', YES);
      picker.set('isAnchored', YES);
      picker.remove();

      var content = ap.getPath('inspectorContentView.content'),
          toolbar = ap.getPath('inspectorContentView.toolbar');

      content.adjust('top', 0);
      toolbar.set('isVisible', NO);
    },

    // ..........................................................
    // Events
    //
    closeInspector: function(){
      this.goState('inspectorClosed');
    },

    toggleDockedInspector: function(){
      this.goState('dockedInspector');
    }
  }),

  dockedInspector: SC.State.create({
    parallelStatechart: 'inspector',

    enterState: function(){
      var iDock = Greenhouse.appPage.get('inspectorDockView');
      iDock.setIfChanged('nowShowing', 'Greenhouse.appPage.inspectorContentView');
    },
    exitState: function(){
      var iDock = Greenhouse.appPage.get('inspectorDockView');
      iDock.setIfChanged('nowShowing', null);
    },

    // ..........................................................
    // Events
    //
    toggleDockedInspector: function(){
      var libState = Greenhouse.get('libraryClosed').state();
      if (libState !== Greenhouse.get('dockedLibrary')) Greenhouse.sendEvent('undock');
      this.goState('inspectorClosed');
    }
  })
});<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Object that controls settings for the workspace.
 * @author fenichel@google.com (Rachel Fenichel)
 */
'use strict';

goog.module('Blockly.Options');
goog.module.declareLegacyNamespace();

goog.require('Blockly.registry');
goog.require('Blockly.Theme');
goog.require('Blockly.Themes.Classic');
goog.require('Blockly.utils.deprecation');
goog.require('Blockly.utils.IdGenerator');
goog.require('Blockly.utils.toolbox');

goog.requireType('Blockly.BlocklyOptions');
goog.requireType('Blockly.utils.Metrics');
goog.requireType('Blockly.WorkspaceSvg');

/**
 * Parse the user-specified options, using reasonable defaults where behaviour
 * is unspecified.
 * @param {!Blockly.BlocklyOptions} options Dictionary of options.
 *     Specification: https://developers.google.com/blockly/guides/get-started/web#configuration
 * @constructor
 */
const Options = function(options) {
  let toolboxJsonDef = null;
  let hasCategories = false;
  let hasTrashcan = false;
  let hasCollapse = false;
  let hasComments = false;
  let hasDisable = false;
  let hasSounds = false;
  const readOnly = !!options['readOnly'];
  if (!readOnly) {
    toolboxJsonDef = Blockly.utils.toolbox.convertToolboxDefToJson(options['toolbox']);
    hasCategories = Blockly.utils.toolbox.hasCategories(toolboxJsonDef);
    hasTrashcan = options['trashcan'];
    if (hasTrashcan === undefined) {
      hasTrashcan = hasCategories;
    }
    hasCollapse = options['collapse'];
    if (hasCollapse === undefined) {
      hasCollapse = hasCategories;
    }
    hasComments = options['comments'];
    if (hasComments === undefined) {
      hasComments = hasCategories;
    }
    hasDisable = options['disable'];
    if (hasDisable === undefined) {
      hasDisable = hasCategories;
    }
    hasSounds = options['sounds'];
    if (hasSounds === undefined) {
      hasSounds = true;
    }
  }

  let maxTrashcanContents = options['maxTrashcanContents'];
  if (hasTrashcan) {
    if (maxTrashcanContents === undefined) {
      maxTrashcanContents = 32;
    }
  } else {
    maxTrashcanContents = 0;
  }
  const rtl = !!options['rtl'];
  let horizontalLayout = options['horizontalLayout'];
  if (horizontalLayout === undefined) {
    horizontalLayout = false;
  }
  let toolboxAtStart = options['toolboxPosition'];
  toolboxAtStart = toolboxAtStart !== 'end';

  /** @type {!Blockly.utils.toolbox.Position} */
  let toolboxPosition;
  if (horizontalLayout) {
    toolboxPosition = toolboxAtStart ?
        Blockly.utils.toolbox.Position.TOP : Blockly.utils.toolbox.Position.BOTTOM;
  } else {
    toolboxPosition = (toolboxAtStart == rtl) ?
        Blockly.utils.toolbox.Position.RIGHT : Blockly.utils.toolbox.Position.LEFT;
  }

  let hasCss = options['css'];
  if (hasCss === undefined) {
    hasCss = true;
  }
  let pathToMedia = 'https://blockly-demo.appspot.com/static/media/';
  if (options['media']) {
    pathToMedia = options['media'];
  } else if (options['path']) {
    // 'path' is a deprecated option which has been replaced by 'media'.
    pathToMedia = options['path'] + 'media/';
  }
  let oneBasedIndex;
  if (options['oneBasedIndex'] === undefined) {
    oneBasedIndex = true;
  } else {
    oneBasedIndex = !!options['oneBasedIndex'];
  }
  const renderer = options['renderer'] || 'geras';

  const plugins = options['plugins'] || {};

  /** @type {boolean} */
  this.RTL = rtl;
  /** @type {boolean} */
  this.oneBasedIndex = oneBasedIndex;
  /** @type {boolean} */
  this.collapse = hasCollapse;
  /** @type {boolean} */
  this.comments = hasComments;
  /** @type {boolean} */
  this.disable = hasDisable;
  /** @type {boolean} */
  this.readOnly = readOnly;
  /** @type {number} */
  this.maxBlocks = options['maxBlocks'] || Infinity;
  /** @type {?Object<string, number>} */
  this.maxInstances = options['maxInstances'];
  /** @type {string} */
  this.pathToMedia = pathToMedia;
  /** @type {boolean} */
  this.hasCategories = hasCategories;
  /** @type {!Options.MoveOptions} */
  this.moveOptions = Options.parseMoveOptions_(options, hasCategories);
  /** @deprecated  January 2019 */
  this.hasScrollbars = !!this.moveOptions.scrollbars;
  /** @type {boolean} */
  this.hasTrashcan = hasTrashcan;
  /** @type {number} */
  this.maxTrashcanContents = maxTrashcanContents;
  /** @type {boolean} */
  this.hasSounds = hasSounds;
  /** @type {boolean} */
  this.hasCss = hasCss;
  /** @type {boolean} */
  this.horizontalLayout = horizontalLayout;
  /** @type {?Blockly.utils.toolbox.ToolboxInfo} */
  this.languageTree = toolboxJsonDef;
  /** @type {!Options.GridOptions} */
  this.gridOptions = Options.parseGridOptions_(options);
  /** @type {!Options.ZoomOptions} */
  this.zoomOptions = Options.parseZoomOptions_(options);
  /** @type {!Blockly.utils.toolbox.Position} */
  this.toolboxPosition = toolboxPosition;
  /** @type {!Blockly.Theme} */
  this.theme = Options.parseThemeOptions_(options);
  /** @type {string} */
  this.renderer = renderer;
  /** @type {?Object} */
  this.rendererOverrides = options['rendererOverrides'];

  /**
   * The SVG element for the grid pattern.
   * Created during injection.
   * @type {?SVGElement}
   */
  this.gridPattern = null;

  /**
   * The parent of the current workspace, or null if there is no parent
   * workspace.  We can assert that this is of type WorkspaceSvg as opposed to
   * Workspace as this is only used in a rendered workspace.
   * @type {Blockly.WorkspaceSvg}
   */
  this.parentWorkspace = options['parentWorkspace'];

  /**
   * Map of plugin type to name of registered plugin or plugin class.
   * @type {!Object<string, (function(new:?, ...?)|string)>}
   */
  this.plugins = plugins;
};

/**
 * Grid Options.
 * @typedef {{
 *     colour: string,
 *     length: number,
 *     snap: boolean,
 *     spacing: number
 * }}
 */
Options.GridOptions;

/**
 * Move Options.
 * @typedef {{
 *     drag: boolean,
 *     scrollbars: (boolean | !Options.ScrollbarOptions),
 *     wheel: boolean
 * }}
 */
Options.MoveOptions;

/**
 * Scrollbar Options.
 * @typedef {{
 *     horizontal: boolean,
 *     vertical: boolean
 * }}
 */
Options.ScrollbarOptions;

/**
 * Zoom Options.
 * @typedef {{
 *     controls: boolean,
 *     maxScale: number,
 *     minScale: number,
 *     pinch: boolean,
 *     scaleSpeed: number,
 *     startScale: number,
 *     wheel: boolean
 * }}
 */
Options.ZoomOptions;

/**
 * If set, sets the translation of the workspace to match the scrollbars.
 * @param {!{x:number,y:number}} xyRatio Contains an x and/or y property which
 *     is a float between 0 and 1 specifying the degree of scrolling.
 * @return {void}
 */
Options.prototype.setMetrics;

/**
 * Return an object with the metrics required to size the workspace.
 * @return {!Blockly.utils.Metrics} Contains size and position metrics.
 */
Options.prototype.getMetrics;

/**
 * Parse the user-specified move options, using reasonable defaults where
 *    behaviour is unspecified.
 * @param {!Object} options Dictionary of options.
 * @param {boolean} hasCategories Whether the workspace has categories or not.
 * @return {!Options.MoveOptions} Normalized move options.
 * @private
 */
Options.parseMoveOptions_ = function(options, hasCategories) {
  const move = options['move'] || {};
  const moveOptions = {};
  if (move['scrollbars'] === undefined && options['scrollbars'] === undefined) {
    moveOptions.scrollbars = hasCategories;
  } else if (typeof move['scrollbars'] == 'object') {
    moveOptions.scrollbars = {};
    moveOptions.scrollbars.horizontal = !!move['scrollbars']['horizontal'];
    moveOptions.scrollbars.vertical = !!move['scrollbars']['vertical'];
    // Convert scrollbars object to boolean if they have the same value.
    // This allows us to easily check for whether any scrollbars exist using
    // !!moveOptions.scrollbars.
    if (moveOptions.scrollbars.horizontal && moveOptions.scrollbars.vertical) {
      moveOptions.scrollbars = true;
    } else if (!moveOptions.scrollbars.horizontal &&
        !moveOptions.scrollbars.vertical) {
      moveOptions.scrollbars = false;
    }
  } else {
    moveOptions.scrollbars = !!move['scrollbars'] || !!options['scrollbars'];
  }

  if (!moveOptions.scrollbars || move['wheel'] === undefined) {
    // Defaults to true if single-direction scroll is enabled.
    moveOptions.wheel = typeof moveOptions.scrollbars == 'object';
  } else {
    moveOptions.wheel = !!move['wheel'];
  }
  if (!moveOptions.scrollbars) {
    moveOptions.drag = false;
  } else if (move['drag'] === undefined) {
    // Defaults to true if scrollbars is true.
    moveOptions.drag = true;
  } else {
    moveOptions.drag = !!move['drag'];
  }
  return moveOptions;
};

/**
 * Parse the user-specified zoom options, using reasonable defaults where
 * behaviour is unspecified.  See zoom documentation:
 *   https://developers.google.com/blockly/guides/configure/web/zoom
 * @param {!Object} options Dictionary of options.
 * @return {!Options.ZoomOptions} Normalized zoom options.
 * @private
 */
Options.parseZoomOptions_ = function(options) {
  const zoom = options['zoom'] || {};
  const zoomOptions = {};
  if (zoom['controls'] === undefined) {
    zoomOptions.controls = false;
  } else {
    zoomOptions.controls = !!zoom['controls'];
  }
  if (zoom['wheel'] === undefined) {
    zoomOptions.wheel = false;
  } else {
    zoomOptions.wheel = !!zoom['wheel'];
  }
  if (zoom['startScale'] === undefined) {
    zoomOptions.startScale = 1;
  } else {
    zoomOptions.startScale = Number(zoom['startScale']);
  }
  if (zoom['maxScale'] === undefined) {
    zoomOptions.maxScale = 3;
  } else {
    zoomOptions.maxScale = Number(zoom['maxScale']);
  }
  if (zoom['minScale'] === undefined) {
    zoomOptions.minScale = 0.3;
  } else {
    zoomOptions.minScale = Number(zoom['minScale']);
  }
  if (zoom['scaleSpeed'] === undefined) {
    zoomOptions.scaleSpeed = 1.2;
  } else {
    zoomOptions.scaleSpeed = Number(zoom['scaleSpeed']);
  }
  if (zoom['pinch'] === undefined) {
    zoomOptions.pinch = zoomOptions.wheel || zoomOptions.controls;
  } else {
    zoomOptions.pinch = !!zoom['pinch'];
  }
  return zoomOptions;
};

/**
 * Parse the user-specified grid options, using reasonable defaults where
 * behaviour is unspecified. See grid documentation:
 *   https://developers.google.com/blockly/guides/configure/web/grid
 * @param {!Object} options Dictionary of options.
 * @return {!Options.GridOptions} Normalized grid options.
 * @private
 */
Options.parseGridOptions_ = function(options) {
  const grid = options['grid'] || {};
  const gridOptions = {};
  gridOptions.spacing = Number(grid['spacing']) || 0;
  gridOptions.colour = grid['colour'] || '#888';
  gridOptions.length =
      (grid['length'] === undefined) ? 1 : Number(grid['length']);
  gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap'];
  return gridOptions;
};

/**
 * Parse the user-specified theme options, using the classic theme as a default.
 *   https://developers.google.com/blockly/guides/configure/web/themes
 * @param {!Object} options Dictionary of options.
 * @return {!Blockly.Theme} A Blockly Theme.
 * @private
 */
Options.parseThemeOptions_ = function(options) {
  const theme = options['theme'] || Blockly.Themes.Classic;
  if (typeof theme == 'string') {
    return /** @type {!Blockly.Theme} */ (
      Blockly.registry.getObject(Blockly.registry.Type.THEME, theme));
  } else if (theme instanceof Blockly.Theme) {
    return /** @type {!Blockly.Theme} */ (theme);
  }
  return Blockly.Theme.defineTheme(theme.name ||
      ('builtin' + Blockly.utils.IdGenerator.getNextUniqueId()), theme);
};

/**
 * Parse the provided toolbox tree into a consistent DOM format.
 * @param {?Node|?string} toolboxDef DOM tree of blocks, or text representation
 *    of same.
 * @return {?Node} DOM tree of blocks, or null.
 * @deprecated Use Blockly.utils.toolbox.parseToolboxTree. (2020 September 28)
 */
Options.parseToolboxTree = function(toolboxDef) {
  Blockly.utils.deprecation.warn(
      'Options.parseToolboxTree',
      'September 2020',
      'September 2021',
      'Blockly.utils.toolbox.parseToolboxTree');
  return Blockly.utils.toolbox.parseToolboxTree(toolboxDef);
};

exports = Options;
<commit_msg>Migrate core/options.js named requires<commit_after>/**
 * @license
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Object that controls settings for the workspace.
 * @author fenichel@google.com (Rachel Fenichel)
 */
'use strict';

goog.module('Blockly.Options');
goog.module.declareLegacyNamespace();

/* eslint-disable-next-line no-unused-vars */
const BlocklyOptions = goog.requireType('Blockly.BlocklyOptions');
const Classic = goog.require('Blockly.Themes.Classic');
const IdGenerator = goog.require('Blockly.utils.IdGenerator');
/* eslint-disable-next-line no-unused-vars */
const Metrics = goog.requireType('Blockly.utils.Metrics');
const Theme = goog.require('Blockly.Theme');
/* eslint-disable-next-line no-unused-vars */
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');
const deprecation = goog.require('Blockly.utils.deprecation');
const registry = goog.require('Blockly.registry');
const toolbox = goog.require('Blockly.utils.toolbox');

/**
 * Parse the user-specified options, using reasonable defaults where behaviour
 * is unspecified.
 * @param {!BlocklyOptions} options Dictionary of options.
 *     Specification: https://developers.google.com/blockly/guides/get-started/web#configuration
 * @constructor
 */
const Options = function(options) {
  let toolboxJsonDef = null;
  let hasCategories = false;
  let hasTrashcan = false;
  let hasCollapse = false;
  let hasComments = false;
  let hasDisable = false;
  let hasSounds = false;
  const readOnly = !!options['readOnly'];
  if (!readOnly) {
    toolboxJsonDef = toolbox.convertToolboxDefToJson(options['toolbox']);
    hasCategories = toolbox.hasCategories(toolboxJsonDef);
    hasTrashcan = options['trashcan'];
    if (hasTrashcan === undefined) {
      hasTrashcan = hasCategories;
    }
    hasCollapse = options['collapse'];
    if (hasCollapse === undefined) {
      hasCollapse = hasCategories;
    }
    hasComments = options['comments'];
    if (hasComments === undefined) {
      hasComments = hasCategories;
    }
    hasDisable = options['disable'];
    if (hasDisable === undefined) {
      hasDisable = hasCategories;
    }
    hasSounds = options['sounds'];
    if (hasSounds === undefined) {
      hasSounds = true;
    }
  }

  let maxTrashcanContents = options['maxTrashcanContents'];
  if (hasTrashcan) {
    if (maxTrashcanContents === undefined) {
      maxTrashcanContents = 32;
    }
  } else {
    maxTrashcanContents = 0;
  }
  const rtl = !!options['rtl'];
  let horizontalLayout = options['horizontalLayout'];
  if (horizontalLayout === undefined) {
    horizontalLayout = false;
  }
  let toolboxAtStart = options['toolboxPosition'];
  toolboxAtStart = toolboxAtStart !== 'end';

  /** @type {!toolbox.Position} */
  let toolboxPosition;
  if (horizontalLayout) {
    toolboxPosition = toolboxAtStart ?
        toolbox.Position.TOP : toolbox.Position.BOTTOM;
  } else {
    toolboxPosition = (toolboxAtStart == rtl) ?
        toolbox.Position.RIGHT : toolbox.Position.LEFT;
  }

  let hasCss = options['css'];
  if (hasCss === undefined) {
    hasCss = true;
  }
  let pathToMedia = 'https://blockly-demo.appspot.com/static/media/';
  if (options['media']) {
    pathToMedia = options['media'];
  } else if (options['path']) {
    // 'path' is a deprecated option which has been replaced by 'media'.
    pathToMedia = options['path'] + 'media/';
  }
  let oneBasedIndex;
  if (options['oneBasedIndex'] === undefined) {
    oneBasedIndex = true;
  } else {
    oneBasedIndex = !!options['oneBasedIndex'];
  }
  const renderer = options['renderer'] || 'geras';

  const plugins = options['plugins'] || {};

  /** @type {boolean} */
  this.RTL = rtl;
  /** @type {boolean} */
  this.oneBasedIndex = oneBasedIndex;
  /** @type {boolean} */
  this.collapse = hasCollapse;
  /** @type {boolean} */
  this.comments = hasComments;
  /** @type {boolean} */
  this.disable = hasDisable;
  /** @type {boolean} */
  this.readOnly = readOnly;
  /** @type {number} */
  this.maxBlocks = options['maxBlocks'] || Infinity;
  /** @type {?Object<string, number>} */
  this.maxInstances = options['maxInstances'];
  /** @type {string} */
  this.pathToMedia = pathToMedia;
  /** @type {boolean} */
  this.hasCategories = hasCategories;
  /** @type {!Options.MoveOptions} */
  this.moveOptions = Options.parseMoveOptions_(options, hasCategories);
  /** @deprecated  January 2019 */
  this.hasScrollbars = !!this.moveOptions.scrollbars;
  /** @type {boolean} */
  this.hasTrashcan = hasTrashcan;
  /** @type {number} */
  this.maxTrashcanContents = maxTrashcanContents;
  /** @type {boolean} */
  this.hasSounds = hasSounds;
  /** @type {boolean} */
  this.hasCss = hasCss;
  /** @type {boolean} */
  this.horizontalLayout = horizontalLayout;
  /** @type {?toolbox.ToolboxInfo} */
  this.languageTree = toolboxJsonDef;
  /** @type {!Options.GridOptions} */
  this.gridOptions = Options.parseGridOptions_(options);
  /** @type {!Options.ZoomOptions} */
  this.zoomOptions = Options.parseZoomOptions_(options);
  /** @type {!toolbox.Position} */
  this.toolboxPosition = toolboxPosition;
  /** @type {!Theme} */
  this.theme = Options.parseThemeOptions_(options);
  /** @type {string} */
  this.renderer = renderer;
  /** @type {?Object} */
  this.rendererOverrides = options['rendererOverrides'];

  /**
   * The SVG element for the grid pattern.
   * Created during injection.
   * @type {?SVGElement}
   */
  this.gridPattern = null;

  /**
   * The parent of the current workspace, or null if there is no parent
   * workspace.  We can assert that this is of type WorkspaceSvg as opposed to
   * Workspace as this is only used in a rendered workspace.
   * @type {WorkspaceSvg}
   */
  this.parentWorkspace = options['parentWorkspace'];

  /**
   * Map of plugin type to name of registered plugin or plugin class.
   * @type {!Object<string, (function(new:?, ...?)|string)>}
   */
  this.plugins = plugins;
};

/**
 * Grid Options.
 * @typedef {{
 *     colour: string,
 *     length: number,
 *     snap: boolean,
 *     spacing: number
 * }}
 */
Options.GridOptions;

/**
 * Move Options.
 * @typedef {{
 *     drag: boolean,
 *     scrollbars: (boolean | !Options.ScrollbarOptions),
 *     wheel: boolean
 * }}
 */
Options.MoveOptions;

/**
 * Scrollbar Options.
 * @typedef {{
 *     horizontal: boolean,
 *     vertical: boolean
 * }}
 */
Options.ScrollbarOptions;

/**
 * Zoom Options.
 * @typedef {{
 *     controls: boolean,
 *     maxScale: number,
 *     minScale: number,
 *     pinch: boolean,
 *     scaleSpeed: number,
 *     startScale: number,
 *     wheel: boolean
 * }}
 */
Options.ZoomOptions;

/**
 * If set, sets the translation of the workspace to match the scrollbars.
 * @param {!{x:number,y:number}} xyRatio Contains an x and/or y property which
 *     is a float between 0 and 1 specifying the degree of scrolling.
 * @return {void}
 */
Options.prototype.setMetrics;

/**
 * Return an object with the metrics required to size the workspace.
 * @return {!Metrics} Contains size and position metrics.
 */
Options.prototype.getMetrics;

/**
 * Parse the user-specified move options, using reasonable defaults where
 *    behaviour is unspecified.
 * @param {!Object} options Dictionary of options.
 * @param {boolean} hasCategories Whether the workspace has categories or not.
 * @return {!Options.MoveOptions} Normalized move options.
 * @private
 */
Options.parseMoveOptions_ = function(options, hasCategories) {
  const move = options['move'] || {};
  const moveOptions = {};
  if (move['scrollbars'] === undefined && options['scrollbars'] === undefined) {
    moveOptions.scrollbars = hasCategories;
  } else if (typeof move['scrollbars'] == 'object') {
    moveOptions.scrollbars = {};
    moveOptions.scrollbars.horizontal = !!move['scrollbars']['horizontal'];
    moveOptions.scrollbars.vertical = !!move['scrollbars']['vertical'];
    // Convert scrollbars object to boolean if they have the same value.
    // This allows us to easily check for whether any scrollbars exist using
    // !!moveOptions.scrollbars.
    if (moveOptions.scrollbars.horizontal && moveOptions.scrollbars.vertical) {
      moveOptions.scrollbars = true;
    } else if (!moveOptions.scrollbars.horizontal &&
        !moveOptions.scrollbars.vertical) {
      moveOptions.scrollbars = false;
    }
  } else {
    moveOptions.scrollbars = !!move['scrollbars'] || !!options['scrollbars'];
  }

  if (!moveOptions.scrollbars || move['wheel'] === undefined) {
    // Defaults to true if single-direction scroll is enabled.
    moveOptions.wheel = typeof moveOptions.scrollbars == 'object';
  } else {
    moveOptions.wheel = !!move['wheel'];
  }
  if (!moveOptions.scrollbars) {
    moveOptions.drag = false;
  } else if (move['drag'] === undefined) {
    // Defaults to true if scrollbars is true.
    moveOptions.drag = true;
  } else {
    moveOptions.drag = !!move['drag'];
  }
  return moveOptions;
};

/**
 * Parse the user-specified zoom options, using reasonable defaults where
 * behaviour is unspecified.  See zoom documentation:
 *   https://developers.google.com/blockly/guides/configure/web/zoom
 * @param {!Object} options Dictionary of options.
 * @return {!Options.ZoomOptions} Normalized zoom options.
 * @private
 */
Options.parseZoomOptions_ = function(options) {
  const zoom = options['zoom'] || {};
  const zoomOptions = {};
  if (zoom['controls'] === undefined) {
    zoomOptions.controls = false;
  } else {
    zoomOptions.controls = !!zoom['controls'];
  }
  if (zoom['wheel'] === undefined) {
    zoomOptions.wheel = false;
  } else {
    zoomOptions.wheel = !!zoom['wheel'];
  }
  if (zoom['startScale'] === undefined) {
    zoomOptions.startScale = 1;
  } else {
    zoomOptions.startScale = Number(zoom['startScale']);
  }
  if (zoom['maxScale'] === undefined) {
    zoomOptions.maxScale = 3;
  } else {
    zoomOptions.maxScale = Number(zoom['maxScale']);
  }
  if (zoom['minScale'] === undefined) {
    zoomOptions.minScale = 0.3;
  } else {
    zoomOptions.minScale = Number(zoom['minScale']);
  }
  if (zoom['scaleSpeed'] === undefined) {
    zoomOptions.scaleSpeed = 1.2;
  } else {
    zoomOptions.scaleSpeed = Number(zoom['scaleSpeed']);
  }
  if (zoom['pinch'] === undefined) {
    zoomOptions.pinch = zoomOptions.wheel || zoomOptions.controls;
  } else {
    zoomOptions.pinch = !!zoom['pinch'];
  }
  return zoomOptions;
};

/**
 * Parse the user-specified grid options, using reasonable defaults where
 * behaviour is unspecified. See grid documentation:
 *   https://developers.google.com/blockly/guides/configure/web/grid
 * @param {!Object} options Dictionary of options.
 * @return {!Options.GridOptions} Normalized grid options.
 * @private
 */
Options.parseGridOptions_ = function(options) {
  const grid = options['grid'] || {};
  const gridOptions = {};
  gridOptions.spacing = Number(grid['spacing']) || 0;
  gridOptions.colour = grid['colour'] || '#888';
  gridOptions.length =
      (grid['length'] === undefined) ? 1 : Number(grid['length']);
  gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap'];
  return gridOptions;
};

/**
 * Parse the user-specified theme options, using the classic theme as a default.
 *   https://developers.google.com/blockly/guides/configure/web/themes
 * @param {!Object} options Dictionary of options.
 * @return {!Theme} A Blockly Theme.
 * @private
 */
Options.parseThemeOptions_ = function(options) {
  const theme = options['theme'] || Classic;
  if (typeof theme == 'string') {
    return /** @type {!Theme} */ (
      registry.getObject(registry.Type.THEME, theme));
  } else if (theme instanceof Theme) {
    return /** @type {!Theme} */ (theme);
  }
  return Theme.defineTheme(theme.name ||
      ('builtin' + IdGenerator.getNextUniqueId()), theme);
};

/**
 * Parse the provided toolbox tree into a consistent DOM format.
 * @param {?Node|?string} toolboxDef DOM tree of blocks, or text representation
 *    of same.
 * @return {?Node} DOM tree of blocks, or null.
 * @deprecated Use toolbox.parseToolboxTree. (2020 September 28)
 */
Options.parseToolboxTree = function(toolboxDef) {
  deprecation.warn(
      'Options.parseToolboxTree',
      'September 2020',
      'September 2021',
      'toolbox.parseToolboxTree');
  return toolbox.parseToolboxTree(toolboxDef);
};

exports = Options;
<|endoftext|>


<commit_before>
//
// Copyright 2016 Kary Foundation, Inc.
//   Author: Pouya Kary <k@karyfoundation.org>
//

//
// ─── MENU ───────────────────────────────────────────────────────────────────────
//

    var MainMenu = [

        //
        // ─── FILE ────────────────────────────────────────────────────────
        //

            {
                label: 'File',
                submenu: [
                    {
                        label: 'New File',
                        accelerator: 'CmdOrCtrl+N',
                        click: onNewFile
                    },
                    {
                        label: 'New Window',
                        accelerator: 'Shift+CmdOrCtrl+N',
                        click: openNewWindow
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Open...',
                        accelerator: 'CmdOrCtrl+O',
                        click: onOpenFile
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Save',
                        accelerator: 'CmdOrCtrl+S',
                        click: ( ) => onSaveFile( )
                    },
                    {
                        label: 'Save As',
                        accelerator: 'Shift+CmdOrCtrl+S',
                        click: onSaveFileAs
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Export to SVG',
                        accelerator: 'CmdOrCtrl+E',
                        click: onExportWorkspaceToSVG
                    },
                ]
            },

        //
        // ─── EDIT MENU ───────────────────────────────────────────────────
        //

            {
                label: 'Edit',
                submenu: [
                    {
                        label: 'Undo',
                        accelerator: 'CmdOrCtrl+Z',
                        role: 'undo'
                    },
                    {
                        label: 'Redo',
                        accelerator: 'Shift+CmdOrCtrl+Z',
                        role: 'redo'
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Cut',
                        accelerator: 'CmdOrCtrl+X',
                        role: 'cut'
                    },
                    {
                        label: 'Copy',
                        accelerator: 'CmdOrCtrl+C',
                        role: 'copy'
                    },
                    {
                        label: 'Paste',
                        accelerator: 'CmdOrCtrl+V',
                        role: 'paste'
                    },
                    {
                        label: 'Select All',
                        accelerator: 'CmdOrCtrl+A',
                        role: 'selectall'
                    }
                ]
            },

        //
        // ─── WINDOW MENU ────────────────────────────────────────────────────────────────
        //

            {
                label: 'Window',
                role: 'window',
                submenu: [
                    {
                        label: 'Minimize',
                        accelerator: 'CmdOrCtrl+M',
                        role: 'minimize'
                    },
                    {
                        label: 'Close',
                        accelerator: 'CmdOrCtrl+W',
                        role: 'close'
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: "Open Developer Tools",
                        click: OrchestraWindow.openDevTools
                    }
                ]
            },

        //
        // ─── HELP MENU ──────────────────────────────────────────────────────────────────
        //

            {
                label: 'Help',
                role: 'help',
                submenu: [
                    {
                        label: "Kary Foundtation's Website",
                        click: ( ) => openExternal('https://www.karyfoundation.org/')
                    },
                    {
                        label: "Report an issue...",
                        click: ( ) => openExternal('https://github.com/karyfoundation/orchestra/issues')
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: "RegExp on Mozilla Developer Network",
                        click: ( ) => openExternal('https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/RegExp')
                    },
                ]
            },

        // ────────────────────────────────────────────────────────────────────────────────

    ];

//
// ─── OS SPECIFIC MENUS ──────────────────────────────────────────────────────────
//

    var AboutPageButton = {
        label: 'About Orchestra',
        click: openAboutPage
    }

    if ( process.platform == 'darwin' ) {
        var name = require( 'electron' ).remote.app.getName( );
        var menuIndex = MainMenu.length - 1;
        MainMenu.unshift(
            {
                label: name,
                submenu: [
                    AboutPageButton,
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Services',
                        role: 'services',
                        submenu: [ ]
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Hide ' + name,
                        accelerator: 'Command+H',
                        role: 'hide'
                    },
                    {
                        label: 'Hide Others',
                        accelerator: 'Command+Alt+H',
                        role: 'hideothers'
                    },
                    {
                        label: 'Show All',
                        role: 'unhide'
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Quit',
                        accelerator: 'Command+Q',
                        click: fireAppQuitRequest
                    },
                ]
            }
        );
    } else {
        MainMenu[ menuIndex ].submenu.push( AboutPageButton );
    }

//
// ─── INIT MENU ──────────────────────────────────────────────────────────────────
//

    function initMainMenu ( ) {
        const remote = require('electron').remote;
        const Menu = remote.Menu;
        const MenuItem = remote.MenuItem;

        OrchestraAppMenu = new Menu();

        OrchestraAppMenu = Menu.buildFromTemplate( MainMenu );
        Menu.setApplicationMenu( OrchestraAppMenu );
    }

//
// ─── ON MENU DISABLE ────────────────────────────────────────────────────────────
//

    function setMenuEnableFactor ( control ) {
        for ( let menuIndex = 0; menuIndex < OrchestraAppMenu.items.length; menuIndex++ ) {
            let submenu = OrchestraAppMenu.items[ menuIndex ].submenu.items;
            for ( let submenuIndex = 0; submenuIndex < submenu.length; submenuIndex++ ) {
                submenu[ submenuIndex ].enabled = control;
            }
        }
    }

// ────────────────────────────────────────────────────────────────────────────────
<commit_msg>more menu items<commit_after>
//
// Copyright 2016 Kary Foundation, Inc.
//   Author: Pouya Kary <k@karyfoundation.org>
//

//
// ─── MENU ───────────────────────────────────────────────────────────────────────
//

    var MainMenu = [

        //
        // ─── FILE ────────────────────────────────────────────────────────
        //

            {
                label: 'File',
                submenu: [
                    {
                        label: 'New File',
                        accelerator: 'CmdOrCtrl+N',
                        click: onNewFile
                    },
                    {
                        label: 'New Window',
                        accelerator: 'Shift+CmdOrCtrl+N',
                        click: openNewWindow
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Open...',
                        accelerator: 'CmdOrCtrl+O',
                        click: onOpenFile
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Save',
                        accelerator: 'CmdOrCtrl+S',
                        click: ( ) => onSaveFile( )
                    },
                    {
                        label: 'Save As',
                        accelerator: 'Shift+CmdOrCtrl+S',
                        click: onSaveFileAs
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Export to SVG',
                        accelerator: 'CmdOrCtrl+E',
                        click: onExportWorkspaceToSVG
                    },
                ]
            },

        //
        // ─── EDIT MENU ───────────────────────────────────────────────────
        //

            {
                label: 'Edit',
                submenu: [
                    {
                        label: 'Undo',
                        accelerator: 'CmdOrCtrl+Z',
                        role: 'undo'
                    },
                    {
                        label: 'Redo',
                        accelerator: 'Shift+CmdOrCtrl+Z',
                        role: 'redo'
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Cut',
                        accelerator: 'CmdOrCtrl+X',
                        role: 'cut'
                    },
                    {
                        label: 'Copy',
                        accelerator: 'CmdOrCtrl+C',
                        role: 'copy'
                    },
                    {
                        label: 'Paste',
                        accelerator: 'CmdOrCtrl+V',
                        role: 'paste'
                    },
                    {
                        label: 'Select All',
                        accelerator: 'CmdOrCtrl+A',
                        role: 'selectall'
                    }
                ]
            },

        //
        // ─── WINDOW MENU ────────────────────────────────────────────────────────────────
        //

            {
                label: 'Window',
                role: 'window',
                submenu: [
                    {
                        label: 'Minimize',
                        accelerator: 'CmdOrCtrl+M',
                        role: 'minimize'
                    },
                    {
                        label: 'Close',
                        accelerator: 'CmdOrCtrl+W',
                        role: 'close'
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: "Open Developer Tools",
                        click: OrchestraWindow.openDevTools
                    }
                ]
            },

        //
        // ─── HELP MENU ──────────────────────────────────────────────────────────────────
        //

            {
                label: 'Help',
                role: 'help',
                submenu: [
                    {
                        label: "Quartet Language's Reference (Offline)",
                        click: onOpenHelpPage
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: "RegExp on ECMA-262 Edition 6.0 (June 2015)",
                        click: ( ) => openExternal('http://www.ecma-international.org/ecma-262/6.0/#sec-regexp-regular-expression-objects')
                    },
                    {
                        label: "RegExp on Mozilla Developer Network",
                        click: ( ) => openExternal('https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/RegExp')
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: "Kary Foundtation's Website",
                        click: ( ) => openExternal('https://www.karyfoundation.org/')
                    },
                    {
                        label: "Report an issue...",
                        click: ( ) => openExternal('https://github.com/karyfoundation/orchestra/issues')
                    },
                    {
                        label: "Orchestra and Quartet on GitHub",
                        click: ( ) => openExternal('https://github.com/karyfoundation/orchestra')
                    },
                ]
            },

        // ────────────────────────────────────────────────────────────────────────────────

    ];

//
// ─── OS SPECIFIC MENUS ──────────────────────────────────────────────────────────
//

    var AboutPageButton = {
        label: 'About Orchestra',
        click: openAboutPage
    }

    if ( process.platform == 'darwin' ) {
        var name = require( 'electron' ).remote.app.getName( );
        var menuIndex = MainMenu.length - 1;
        MainMenu.unshift(
            {
                label: name,
                submenu: [
                    AboutPageButton,
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Services',
                        role: 'services',
                        submenu: [ ]
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Hide ' + name,
                        accelerator: 'Command+H',
                        role: 'hide'
                    },
                    {
                        label: 'Hide Others',
                        accelerator: 'Command+Alt+H',
                        role: 'hideothers'
                    },
                    {
                        label: 'Show All',
                        role: 'unhide'
                    },
                    {
                        type: 'separator'
                    },
                    {
                        label: 'Quit',
                        accelerator: 'Command+Q',
                        click: fireAppQuitRequest
                    },
                ]
            }
        );
    } else {
        MainMenu[ menuIndex ].submenu.push( AboutPageButton );
    }

//
// ─── INIT MENU ──────────────────────────────────────────────────────────────────
//

    function initMainMenu ( ) {
        const remote = require('electron').remote;
        const Menu = remote.Menu;
        const MenuItem = remote.MenuItem;

        OrchestraAppMenu = new Menu();

        OrchestraAppMenu = Menu.buildFromTemplate( MainMenu );
        Menu.setApplicationMenu( OrchestraAppMenu );
    }

//
// ─── ON MENU DISABLE ────────────────────────────────────────────────────────────
//

    function setMenuEnableFactor ( control ) {
        for ( let menuIndex = 0; menuIndex < OrchestraAppMenu.items.length; menuIndex++ ) {
            let submenu = OrchestraAppMenu.items[ menuIndex ].submenu.items;
            for ( let submenuIndex = 0; submenuIndex < submenu.length; submenuIndex++ ) {
                submenu[ submenuIndex ].enabled = control;
            }
        }
    }

// ────────────────────────────────────────────────────────────────────────────────
<|endoftext|>