<commit_before>import EmberRouter from '@ember/routing/router';
import config from 'ghost-admin/config/environment';
import ghostPaths from 'ghost-admin/utils/ghost-paths';

const Router = EmberRouter.extend({
    location: config.locationType, // use HTML5 History API instead of hash-tag based URLs
    rootURL: ghostPaths().adminRoot // admin interface lives under sub-directory /ghost
});

Router.map(function () {
    this.route('home', {path: '/'});

    this.route('setup', function () {
        this.route('one');
        this.route('two');
        this.route('three');
    });

    this.route('signin');
    this.route('signout');
    this.route('signup', {path: '/signup/:token'});
    this.route('reset', {path: '/reset/:token'});

    this.route('about');
    this.route('site');
    this.route('dashboard');
    this.route('launch');

    this.route('pro', function () {
        this.route('pro-sub', {path: '/*sub'});
    });

    this.route('posts');
    this.route('pages');

    this.route('editor', function () {
        this.route('new', {path: ':type'});
        this.route('edit', {path: ':type/:post_id'});
    });

    this.route('staff', function () {
        this.route('user', {path: ':user_slug'});
    });

    this.route('tags');
    this.route('tag.new', {path: '/tags/new'});
    this.route('tag', {path: '/tags/:tag_slug'});

    this.route('settings');
    this.route('settings.general', {path: '/settings/general'});
    this.route('settings.membership', {path: '/settings/members'});
    this.route('settings.members-email', {path: '/settings/members-email'});
    this.route('settings.code-injection', {path: '/settings/code-injection'});

    this.route('settings.products', {path: '/settings/products'});
    this.route('settings.product.new', {path: '/settings/product/new'});
    this.route('settings.product', {path: '/settings/product/:product_id'});

    this.route('settings.theme', {path: '/settings/theme'}, function () {
        this.route('uploadtheme');
        this.route('install');
    });
    this.route('settings.navigation', {path: '/settings/navigation'});
    this.route('settings.labs', {path: '/settings/labs'});

    this.route('integrations', function () {
        this.route('new');
    });
    this.route('integration', {path: '/integrations/:integration_id'}, function () {
        this.route('webhooks.new', {path: 'webhooks/new'});
        this.route('webhooks.edit', {path: 'webhooks/:webhook_id'});
    });
    this.route('integrations.slack', {path: '/integrations/slack'});
    this.route('integrations.amp', {path: '/integrations/amp'});
    this.route('integrations.firstpromoter', {path: '/integrations/firstpromoter'});
    this.route('integrations.unsplash', {path: '/integrations/unsplash'});
    this.route('integrations.zapier', {path: '/integrations/zapier'});

    this.route('members', function () {
        this.route('import');
    });
    this.route('member.new', {path: '/members/new'});
    this.route('member', {path: '/members/:member_id'});

    this.route('error404', {path: '/*path'});

    this.route('designsandbox');
});

export default Router;
<commit_msg>Commented out temporarily unused products routes<commit_after>import EmberRouter from '@ember/routing/router';
import config from 'ghost-admin/config/environment';
import ghostPaths from 'ghost-admin/utils/ghost-paths';

const Router = EmberRouter.extend({
    location: config.locationType, // use HTML5 History API instead of hash-tag based URLs
    rootURL: ghostPaths().adminRoot // admin interface lives under sub-directory /ghost
});

Router.map(function () {
    this.route('home', {path: '/'});

    this.route('setup', function () {
        this.route('one');
        this.route('two');
        this.route('three');
    });

    this.route('signin');
    this.route('signout');
    this.route('signup', {path: '/signup/:token'});
    this.route('reset', {path: '/reset/:token'});

    this.route('about');
    this.route('site');
    this.route('dashboard');
    this.route('launch');

    this.route('pro', function () {
        this.route('pro-sub', {path: '/*sub'});
    });

    this.route('posts');
    this.route('pages');

    this.route('editor', function () {
        this.route('new', {path: ':type'});
        this.route('edit', {path: ':type/:post_id'});
    });

    this.route('staff', function () {
        this.route('user', {path: ':user_slug'});
    });

    this.route('tags');
    this.route('tag.new', {path: '/tags/new'});
    this.route('tag', {path: '/tags/:tag_slug'});

    this.route('settings');
    this.route('settings.general', {path: '/settings/general'});
    this.route('settings.membership', {path: '/settings/members'});
    this.route('settings.members-email', {path: '/settings/members-email'});
    this.route('settings.code-injection', {path: '/settings/code-injection'});

    // this.route('settings.products', {path: '/settings/products'});
    // this.route('settings.product.new', {path: '/settings/product/new'});
    // this.route('settings.product', {path: '/settings/product/:product_id'});

    this.route('settings.theme', {path: '/settings/theme'}, function () {
        this.route('uploadtheme');
        this.route('install');
    });
    this.route('settings.navigation', {path: '/settings/navigation'});
    this.route('settings.labs', {path: '/settings/labs'});

    this.route('integrations', function () {
        this.route('new');
    });
    this.route('integration', {path: '/integrations/:integration_id'}, function () {
        this.route('webhooks.new', {path: 'webhooks/new'});
        this.route('webhooks.edit', {path: 'webhooks/:webhook_id'});
    });
    this.route('integrations.slack', {path: '/integrations/slack'});
    this.route('integrations.amp', {path: '/integrations/amp'});
    this.route('integrations.firstpromoter', {path: '/integrations/firstpromoter'});
    this.route('integrations.unsplash', {path: '/integrations/unsplash'});
    this.route('integrations.zapier', {path: '/integrations/zapier'});

    this.route('members', function () {
        this.route('import');
    });
    this.route('member.new', {path: '/members/new'});
    this.route('member', {path: '/members/:member_id'});

    this.route('error404', {path: '/*path'});

    this.route('designsandbox');
});

export default Router;
<|endoftext|>


<commit_before>/*global window, document, Ghost, $, _, Backbone, JST, NProgress */
(function () {
    "use strict";

    var ContentList,
        ContentItem,
        PreviewContainer;

    // Base view
    // ----------
    Ghost.Views.Blog = Ghost.View.extend({
        initialize: function (options) {
            /*jslint unparam:true*/
            this.listenTo(this.collection, 'request', function () {
                NProgress.start();
            });
            this.listenTo(this.collection, 'sync', function () {
                NProgress.done();
            });
            this.addSubview(new PreviewContainer({ el: '.js-content-preview', collection: this.collection })).render();
            this.addSubview(new ContentList({ el: '.js-content-list', collection: this.collection })).render();
        }
    });

    // Content list (sidebar)
    // -----------------------
    ContentList = Ghost.View.extend({

        isLoading: false,

        events: {
            'click .content-list-content'    : 'scrollHandler'
        },

        initialize: function () {
            this.$('.content-list-content').scrollClass({target: '.content-list', offset: 10});
            this.listenTo(this.collection, 'remove', this.showNext);
            this.listenTo(this.collection, 'add', this.renderPost);
            // Can't use backbone event bind (see: http://stackoverflow.com/questions/13480843/backbone-scroll-event-not-firing)
            this.$('.content-list-content').scroll($.proxy(this.checkScroll, this));
        },

        showNext: function () {
            if (this.isLoading) { return; }

            if (!this.collection.length) {
                return Backbone.trigger('blog:activeItem', null);
            }

            var id = this.collection.at(0) ? this.collection.at(0).id : false;
            if (id) {
                Backbone.trigger('blog:activeItem', id);
            }
        },

        reportLoadError: function (response) {
            var message = 'A problem was encountered while loading more posts';

            if (response) {
                // Get message from response
                message += '; ' + Ghost.Views.Utils.getRequestErrorMessage(response);
            } else {
                message += '.';
            }

            Ghost.notifications.addItem({
                type: 'error',
                message: message,
                status: 'passive'
            });
        },

        checkScroll: function (event) {
            var self = this,
                element = event.target,
                triggerPoint = 100;

            // If we haven't passed our threshold, exit
            if (this.isLoading || (element.scrollTop + element.clientHeight + triggerPoint <= element.scrollHeight)) {
                return;
            }

            // If we've loaded the max number of pages, exit
            if (this.collection.currentPage >=  this.collection.totalPages) {
                return;
            }

            // Load moar posts!
            this.isLoading = true;
            this.collection.fetch({
                update: true,
                remove: false,
                data: {
                    status: 'all',
                    page: (self.collection.currentPage + 1),
                    staticPages: 'all'
                }
            }).then(function onSuccess(response) {
                /*jslint unparam:true*/
                self.render();
                self.isLoading = false;
            }, function onError(e) {
                self.reportLoadError(e);
            });
        },

        renderPost: function (model) {
            this.$('ol').append(this.addSubview(new ContentItem({model: model})).render().el);
        },

        render: function () {
            var $list = this.$('ol');

            // Clear out any pre-existing subviews.
            this.removeSubviews();

            this.collection.each(function (model) {
                $list.append(this.addSubview(new ContentItem({model: model})).render().el);
            }, this);
            this.showNext();
        }

    });

    // Content Item
    // -----------------------
    ContentItem = Ghost.View.extend({

        tagName: 'li',

        events: {
            'click a': 'setActiveItem'
        },

        active: false,

        initialize: function () {
            this.listenTo(Backbone, 'blog:activeItem', this.checkActive);
            this.listenTo(this.model, 'change:page change:featured', this.render);
            this.listenTo(this.model, 'destroy', this.removeItem);
        },

        removeItem: function () {
            var self = this;
            $.when(this.$el.slideUp()).then(function () {
                self.remove();
            });
        },

        // If the current item isn't active, we trigger the event to
        // notify a change in which item we're viewing.
        setActiveItem: function (e) {
            e.preventDefault();
            if (this.active !== true) {
                Backbone.trigger('blog:activeItem', this.model.id);
                this.render();
            }
        },

        // Checks whether this item is active and doesn't match the current id.
        checkActive: function (id) {
            if (this.model.id !== id) {
                if (this.active) {
                    this.active = false;
                    this.$el.removeClass('active');
                    this.render();
                }
            } else {
                this.active = true;
                this.$el.addClass('active');
            }
        },

        showPreview: function (e) {
            var item = $(e.currentTarget);
            this.$('.content-list-content li').removeClass('active');
            item.addClass('active');
            Backbone.trigger('blog:activeItem', item.data('id'));
        },

        templateName: "list-item",

        templateData: function () {
            return _.extend({active: this.active}, this.model.toJSON());
        }
    });

    // Content preview
    // ----------------
    PreviewContainer = Ghost.View.extend({

        activeId: null,

        events: {
            'click .post-controls .post-edit' : 'editPost',
            'click .featured' : 'toggleFeatured',
            'click .unfeatured' : 'toggleFeatured'
        },

        initialize: function () {
            this.listenTo(Backbone, 'blog:activeItem', this.setActivePreview);
        },

        setActivePreview: function (id) {
            if (this.activeId !== id) {
                this.activeId = id;
                this.render();
            }
        },

        editPost: function (e) {
            e.preventDefault();
            // for now this will disable "open in new tab", but when we have a Router implemented
            // it can go back to being a normal link to '#/ghost/editor/X'
            window.location = Ghost.paths.subdir + '/ghost/editor/' + this.model.get('id') + '/';
        },

        toggleFeatured: function (e) {
            var self = this,
                featured = !self.model.get('featured'),
                featuredEl = $(e.currentTarget),
                model = this.collection.get(this.activeId);

            model.save({
                featured: featured
            }, {
                success : function () {
                    featuredEl.removeClass("featured unfeatured").addClass(featured ? "featured" : "unfeatured");
                    Ghost.notifications.addItem({
                        type: 'success',
                        message: "Post successfully marked as " + (featured ? "featured" : "not featured") + ".",
                        status: 'passive'
                    });
                },
                error : function (model, xhr) {
                    /*jslint unparam:true*/
                    Ghost.notifications.addItem({
                        type: 'error',
                        message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
                        status: 'passive'
                    });
                }
            });
        },

        templateName: "preview",

        render: function () {
            this.model = this.collection.get(this.activeId);
            this.$el.html(this.template(this.templateData()));

            this.$('.content-preview-content').scrollClass({target: '.content-preview', offset: 10});
            this.$('.wrapper').on('click', 'a', function (e) {
                $(e.currentTarget).attr('target', '_blank');
            });

            if (this.model !== undefined) {
                this.addSubview(new Ghost.View.PostSettings({el: $('.post-controls'), model: this.model})).render();
            }

            Ghost.temporary.initToggles(this.$el);
            return this;
        }

    });

}());
<commit_msg>Fix hanging loading bar on deleting a post<commit_after>/*global window, document, Ghost, $, _, Backbone, JST, NProgress */
(function () {
    "use strict";

    var ContentList,
        ContentItem,
        PreviewContainer;

    // Base view
    // ----------
    Ghost.Views.Blog = Ghost.View.extend({
        initialize: function (options) {
            /*jslint unparam:true*/
            var self = this,
                finishProgress = function () {
                    NProgress.done();
                };

            // Basic collection request/sync flow progress bar handlers
            this.listenTo(this.collection, 'request', function () {
                NProgress.start();
            });
            this.listenTo(this.collection, 'sync', finishProgress);

            // A special case because models that are destroyed are removed from the
            // collection before the sync event fires and bubbles up
            this.listenTo(this.collection, 'destroy', function (model) {
                self.listenToOnce(model, 'sync', finishProgress);
            });

            this.addSubview(new PreviewContainer({ el: '.js-content-preview', collection: this.collection })).render();
            this.addSubview(new ContentList({ el: '.js-content-list', collection: this.collection })).render();
        }
    });

    // Content list (sidebar)
    // -----------------------
    ContentList = Ghost.View.extend({

        isLoading: false,

        events: {
            'click .content-list-content'    : 'scrollHandler'
        },

        initialize: function () {
            this.$('.content-list-content').scrollClass({target: '.content-list', offset: 10});
            this.listenTo(this.collection, 'remove', this.showNext);
            this.listenTo(this.collection, 'add', this.renderPost);
            // Can't use backbone event bind (see: http://stackoverflow.com/questions/13480843/backbone-scroll-event-not-firing)
            this.$('.content-list-content').scroll($.proxy(this.checkScroll, this));
        },

        showNext: function () {
            if (this.isLoading) { return; }

            if (!this.collection.length) {
                return Backbone.trigger('blog:activeItem', null);
            }

            var id = this.collection.at(0) ? this.collection.at(0).id : false;
            if (id) {
                Backbone.trigger('blog:activeItem', id);
            }
        },

        reportLoadError: function (response) {
            var message = 'A problem was encountered while loading more posts';

            if (response) {
                // Get message from response
                message += '; ' + Ghost.Views.Utils.getRequestErrorMessage(response);
            } else {
                message += '.';
            }

            Ghost.notifications.addItem({
                type: 'error',
                message: message,
                status: 'passive'
            });
        },

        checkScroll: function (event) {
            var self = this,
                element = event.target,
                triggerPoint = 100;

            // If we haven't passed our threshold, exit
            if (this.isLoading || (element.scrollTop + element.clientHeight + triggerPoint <= element.scrollHeight)) {
                return;
            }

            // If we've loaded the max number of pages, exit
            if (this.collection.currentPage >=  this.collection.totalPages) {
                return;
            }

            // Load moar posts!
            this.isLoading = true;
            this.collection.fetch({
                update: true,
                remove: false,
                data: {
                    status: 'all',
                    page: (self.collection.currentPage + 1),
                    staticPages: 'all'
                }
            }).then(function onSuccess(response) {
                /*jslint unparam:true*/
                self.render();
                self.isLoading = false;
            }, function onError(e) {
                self.reportLoadError(e);
            });
        },

        renderPost: function (model) {
            this.$('ol').append(this.addSubview(new ContentItem({model: model})).render().el);
        },

        render: function () {
            var $list = this.$('ol');

            // Clear out any pre-existing subviews.
            this.removeSubviews();

            this.collection.each(function (model) {
                $list.append(this.addSubview(new ContentItem({model: model})).render().el);
            }, this);
            this.showNext();
        }

    });

    // Content Item
    // -----------------------
    ContentItem = Ghost.View.extend({

        tagName: 'li',

        events: {
            'click a': 'setActiveItem'
        },

        active: false,

        initialize: function () {
            this.listenTo(Backbone, 'blog:activeItem', this.checkActive);
            this.listenTo(this.model, 'change:page change:featured', this.render);
            this.listenTo(this.model, 'destroy', this.removeItem);
        },

        removeItem: function () {
            var self = this;
            $.when(this.$el.slideUp()).then(function () {
                self.remove();
            });
        },

        // If the current item isn't active, we trigger the event to
        // notify a change in which item we're viewing.
        setActiveItem: function (e) {
            e.preventDefault();
            if (this.active !== true) {
                Backbone.trigger('blog:activeItem', this.model.id);
                this.render();
            }
        },

        // Checks whether this item is active and doesn't match the current id.
        checkActive: function (id) {
            if (this.model.id !== id) {
                if (this.active) {
                    this.active = false;
                    this.$el.removeClass('active');
                    this.render();
                }
            } else {
                this.active = true;
                this.$el.addClass('active');
            }
        },

        showPreview: function (e) {
            var item = $(e.currentTarget);
            this.$('.content-list-content li').removeClass('active');
            item.addClass('active');
            Backbone.trigger('blog:activeItem', item.data('id'));
        },

        templateName: "list-item",

        templateData: function () {
            return _.extend({active: this.active}, this.model.toJSON());
        }
    });

    // Content preview
    // ----------------
    PreviewContainer = Ghost.View.extend({

        activeId: null,

        events: {
            'click .post-controls .post-edit' : 'editPost',
            'click .featured' : 'toggleFeatured',
            'click .unfeatured' : 'toggleFeatured'
        },

        initialize: function () {
            this.listenTo(Backbone, 'blog:activeItem', this.setActivePreview);
        },

        setActivePreview: function (id) {
            if (this.activeId !== id) {
                this.activeId = id;
                this.render();
            }
        },

        editPost: function (e) {
            e.preventDefault();
            // for now this will disable "open in new tab", but when we have a Router implemented
            // it can go back to being a normal link to '#/ghost/editor/X'
            window.location = Ghost.paths.subdir + '/ghost/editor/' + this.model.get('id') + '/';
        },

        toggleFeatured: function (e) {
            var self = this,
                featured = !self.model.get('featured'),
                featuredEl = $(e.currentTarget),
                model = this.collection.get(this.activeId);

            model.save({
                featured: featured
            }, {
                success : function () {
                    featuredEl.removeClass("featured unfeatured").addClass(featured ? "featured" : "unfeatured");
                    Ghost.notifications.addItem({
                        type: 'success',
                        message: "Post successfully marked as " + (featured ? "featured" : "not featured") + ".",
                        status: 'passive'
                    });
                },
                error : function (model, xhr) {
                    /*jslint unparam:true*/
                    Ghost.notifications.addItem({
                        type: 'error',
                        message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
                        status: 'passive'
                    });
                }
            });
        },

        templateName: "preview",

        render: function () {
            this.model = this.collection.get(this.activeId);
            this.$el.html(this.template(this.templateData()));

            this.$('.content-preview-content').scrollClass({target: '.content-preview', offset: 10});
            this.$('.wrapper').on('click', 'a', function (e) {
                $(e.currentTarget).attr('target', '_blank');
            });

            if (this.model !== undefined) {
                this.addSubview(new Ghost.View.PostSettings({el: $('.post-controls'), model: this.model})).render();
            }

            Ghost.temporary.initToggles(this.$el);
            return this;
        }

    });

}());
<|endoftext|>


<commit_before>window.onload = function () {
	document.getElementById("play").onclick = function(){arshsim();};
}<commit_msg>Added a comment to denote what controller.js does.<commit_after>//This makes the play button work.
window.onload = function () {
	document.getElementById("play").onclick = function(){arshsim();};
}
<|endoftext|>


<commit_before>/* eslint-disable no-console */
import path from 'path';
import fse from 'fs-extra';

const files = [
  'README.md',
  'CHANGELOG.md',
  'LICENSE',
];

Promise.all(
  files.map((file) => copyFile(file))
)
.then(() => createPackageFile());

function copyFile(file) {
  const buildPath = resolveBuildPath(file);
  return new Promise((resolve) => {
    fse.copy(
      file,
      buildPath,
      (err) => {
        if (err) throw err;
        resolve();
      }
    );
  })
  .then(() => console.log(`Copied ${file} to ${buildPath}`));
}

function resolveBuildPath(file) {
  return path.resolve(__dirname, '../build/', path.basename(file));
}

function createPackageFile() {
  return new Promise((resolve) => {
    fse.readFile(path.resolve(__dirname, '../package.json'), 'utf8', (err, data) => {
      if (err) {
        throw err;
      }

      resolve(data);
    });
  })
  .then((data) => JSON.parse(data))
  .then((packageData) => {
    const {
      author,
      version,
      description,
      keywords,
      repository,
      license,
      bugs,
      homepage,
      peerDependencies,
      dependencies,
    } = packageData;

    const minimalPackage = {
      name: 'material-ui',
      author,
      version,
      description,
      main: './index.js',
      keywords,
      repository,
      license,
      bugs,
      homepage,
      peerDependencies,
      dependencies,
    };

    return new Promise((resolve) => {
      const buildPath = path.resolve(__dirname, '../build/package.json');
      const data = JSON.stringify(minimalPackage, null, 2);
      fse.writeFile(buildPath, data, (err) => {
        if (err) throw (err);
        console.log(`Created package.json in ${buildPath}`);
        resolve();
      });
    });
  });
}
<commit_msg>Add support Webpack 2/Rollup tree shaking<commit_after>/* eslint-disable no-console */
import path from 'path';
import fse from 'fs-extra';

const files = [
  'README.md',
  'CHANGELOG.md',
  'LICENSE',
];

Promise.all(
  files.map((file) => copyFile(file))
)
.then(() => copyIndexFile())
.then(() => createPackageFile());

function copyFile(file) {
  const buildPath = resolveBuildPath(file);
  return new Promise((resolve) => {
    fse.copy(
      file,
      buildPath,
      (err) => {
        if (err) throw err;
        resolve();
      }
    );
  })
  .then(() => console.log(`Copied ${file} to ${buildPath}`));
}

function copyIndexFile() {
  return new Promise((resolve, reject) => {
    fse.copy(path.resolve(__dirname, '../src/index.js'), resolveBuildPath('index.es.js'), (err) => {
      if (err) {
        reject(err)
      } else {
        resolve()
      }
    });
  })
}

function resolveBuildPath(file) {
  return path.resolve(__dirname, '../build/', path.basename(file));
}

function createPackageFile() {
  return new Promise((resolve) => {
    fse.readFile(path.resolve(__dirname, '../package.json'), 'utf8', (err, data) => {
      if (err) {
        throw err;
      }

      resolve(data);
    });
  })
  .then((data) => JSON.parse(data))
  .then((packageData) => {
    const {
      author,
      version,
      description,
      keywords,
      repository,
      license,
      bugs,
      homepage,
      peerDependencies,
      dependencies,
    } = packageData;

    const minimalPackage = {
      name: 'material-ui',
      author,
      version,
      description,
      main: './index.js',
      module: './index.es.js',
      'jsnext:main': './index.es.js',
      keywords,
      repository,
      license,
      bugs,
      homepage,
      peerDependencies,
      dependencies,
    };

    return new Promise((resolve) => {
      const buildPath = path.resolve(__dirname, '../build/package.json');
      const data = JSON.stringify(minimalPackage, null, 2);
      fse.writeFile(buildPath, data, (err) => {
        if (err) throw (err);
        console.log(`Created package.json in ${buildPath}`);
        resolve();
      });
    });
  });
}
<|endoftext|>


<commit_before>export const _slice = [].slice;

/**
 * Compare two arrays for equality
 *
 * @private
 * @param {Array} a
 * @param {Array} b
 * @returns {boolean}
 */
function _arrEquals(a, b) {
	return a.length == b.length &&
		a.every(function(el, i) {
			return _equals(el, b[i]);
		});
}

/**
 * Cast string to most applicable data type
 *
 * @protected
 * @param {*} val
 */
export function _castString(val) {
	if (typeof val == 'string') {
		try {
			val = val == 'true' ? true :
				val == 'false' ? false :
					val == 'null' ? null :
						parseInt(val).toString() == val ? parseInt(val) :
							/^(?:\{[\w\W]*}|\[[\w\W]*])$/.test(val) ? JSON.parse(val) :
								val;
		} catch (e) {}
	}

	return val;
}

/**
 * Clone value to a new instance
 *
 * @private
 * @param {*} val
 * @returns {*}
 */
function _copy(val) {
	let type = $type(val);

	if (type == 'object') {
		val = _extend({}, val, true);
	} else if (type == 'array') {
		val = val.slice(0);
	}

	return val;
}

/**
 * Compare two values for equality
 *
 * @private
 * @param {*} a
 * @param {*} b
 * @returns {boolean}
 */
function _equals(a, b) {
	if (a === b) {
		return true;
	}

	let aType = $type(a);

	if (aType != $type(b)) {
		return false;
	}

	if (aType == 'array') {
		return _arrEquals(a, b);
	}

	if (aType == 'object') {
		return _objEquals(a, b);
	}

	if (aType == 'date') {
		return +a == +b;
	}

	return false;
}

/**
 * Extend target object with source object(s)
 *
 * @private
 * @param {Object} target
 * @param {Object} object
 * @param {boolean} [deep=false]
 * @param {Array} [_set=[]]
 * @returns {Object}
 */
export function _extend(target, object, deep, _set = []) {
	if (! object) {
		return target;
	}

	for (let key in object) {
		let src = object[key],
			type = $type(src);

		if (deep && type == 'object') {
			let len = _set.length,
				i = 0,
				val;

			for (; i < len; i++) {
				if (_set[i] === src) {
					val = src;
					break;
				}
			}

			if (val) {
				target[key] = val;
			} else {
				_set.push(src);
				target[key] = _extend(target[key] || {}, src, deep, _set);
			}
		} else if (src !== undefined) {
			target[key] = type == 'array' ? src.slice(0) : src;
		}
	}

	return target;
}

/**
 * Compare two objects for equality
 *
 * @private
 * @param {Object} a
 * @param {Object} b
 * @returns {boolean}
 */
function _objEquals(a, b) {
	let aKeys = Object.keys(a);

	return _arrEquals(aKeys.sort(), Object.keys(b).sort()) &&
		aKeys.every(function(i) {
			return _equals(a[i], b[i]);
		});
}

/**
 * Clone value to a new instance
 *
 * @private
 * @param {*} val
 * @returns {*}
 */
export function $copy(val) {
	return _copy(val);
}

/**
 * Compare two values for strict equality
 *
 * @param {*} a
 * @param {*} b
 * @returns {boolean}
 */
export function $equals(a, b) {
	return _equals(a, b);
}

/**
 * Extend target object with source object(s)
 *
 * @param {(boolean|Object)} deep - extend nested properties else target object
 * @param {Object} [obj] - target object
 * @param {...Object} [obj] - merged objects
 * @returns {Object}
 */
export function $extend(deep) {
	let bool = typeof deep == 'boolean',
		args = _slice.call(arguments).slice(bool ? 1 : 0),
		target = args[0] || {};
	deep = bool ? deep : false;

	args.slice(1).forEach(function(source) {
		target = _extend(target, source, deep);
	});

	return target;
}

/**
 * Determine if value is an array
 *
 * @param {*} obj
 * @returns {boolean}
 */
export function $isArray(obj) {
	return Array.isArray(obj);
}

/**
 * Determine if value is a function
 *
 * @param {*} obj
 * @returns {boolean}
 */
export function $isFunction(obj) {
	return $type(obj) == 'function';
}

/**
 * Determine if value is a number (optional loose match)
 *
 * @param {*} obj
 * @param {boolean} [strict]
 * @returns {boolean}
 */
export function $isNumber(obj, strict = true) {
	if (! strict) {
		if (! obj.match(/^\d*\.?\d*$/g)) {
			return false;
		}

		let value = parseFloat(obj);

		// If value = NaN, will not be equal
		return value === value;
	}

	return $type(obj) == 'number';
}

/**
 * Determine if value is an object
 *
 * @param {*} obj
 * @returns {boolean}
 */
export function $isObject(obj) {
	return $type(obj) == 'object';
}

/**
 * Determine if value is a string
 *
 * @param {*} obj
 * @returns {boolean}
 */
export function $isString(obj) {
	return typeof obj == 'string';
}

/**
 * Serialize object
 *
 * @param {Object} obj
 * @returns {string} value
 */
export function $serialize(obj) {
	let arr = [];

	Object.keys(obj || {}).forEach(key => {
		let val = obj[key];
		key = encodeURIComponent(key);

		if (typeof val != 'object') {
			arr.push(key + '=' + encodeURIComponent(val));
		} else if (Array.isArray(val)) {
			val.forEach(function(el) {
				arr.push(key + '[]=' + encodeURIComponent(el));
			});
		}
	});

	return arr.length ? arr.join('&').replace(/%20/g, '+') : '';
}

/**
 * Cast value to array if it isn't one
 *
 * @param {*} val
 * @returns {Array} value
 */
export function $toArray(val) {
	return val !== undefined ? (Array.isArray(val) ? val : [val]) : [];
}

/**
 * Determine the JavaScript type of an object
 *
 * @param {*} obj
 * @returns string
 */
export function $type(obj) {
	return obj === undefined ? 'undefined' :
		Object.prototype.toString.call(obj)
			.replace(/^\[object (.+)]$/, '$1')
			.toLowerCase();
}

/**
 * Convert serialized string back into an object
 *
 * @param {string} str
 * @returns {Object} value
 */
export function $unserialize(str) {
	let obj = {};

	decodeURIComponent(str)
		.replace(/^\?/, '')
		.split('&').forEach(function(el) {
		let split = el.split('='),
			key = split[0].replace('[]', ''),
			val = (split[1] || '').replace(/\+/g, ' ') || '';

		if (obj.hasOwnProperty(key)) {
			obj[key] = $toArray(obj[key]);
			obj[key].push(_castString(val));
		} else {
			obj[key] = _castString(val);
		}
	});

	return obj;
}<commit_msg>Add strict equality to _castString<commit_after>export const _slice = [].slice;

/**
 * Compare two arrays for equality
 *
 * @private
 * @param {Array} a
 * @param {Array} b
 * @returns {boolean}
 */
function _arrEquals(a, b) {
	return a.length == b.length &&
		a.every(function(el, i) {
			return _equals(el, b[i]);
		});
}

/**
 * Cast string to most applicable data type
 *
 * @protected
 * @param {*} val
 */
export function _castString(val) {
	if (typeof val === 'string') {
		try {
			val = val === 'true' ? true :
				val === 'false' ? false :
					val === 'null' ? null :
						parseInt(val).toString() === val ? parseInt(val) :
							/^(?:\{[\w\W]*}|\[[\w\W]*])$/.test(val) ? JSON.parse(val) :
								val;
		} catch (e) {}
	}

	return val;
}

/**
 * Clone value to a new instance
 *
 * @private
 * @param {*} val
 * @returns {*}
 */
function _copy(val) {
	let type = $type(val);

	if (type == 'object') {
		val = _extend({}, val, true);
	} else if (type == 'array') {
		val = val.slice(0);
	}

	return val;
}

/**
 * Compare two values for equality
 *
 * @private
 * @param {*} a
 * @param {*} b
 * @returns {boolean}
 */
function _equals(a, b) {
	if (a === b) {
		return true;
	}

	let aType = $type(a);

	if (aType != $type(b)) {
		return false;
	}

	if (aType == 'array') {
		return _arrEquals(a, b);
	}

	if (aType == 'object') {
		return _objEquals(a, b);
	}

	if (aType == 'date') {
		return +a == +b;
	}

	return false;
}

/**
 * Extend target object with source object(s)
 *
 * @private
 * @param {Object} target
 * @param {Object} object
 * @param {boolean} [deep=false]
 * @param {Array} [_set=[]]
 * @returns {Object}
 */
export function _extend(target, object, deep, _set = []) {
	if (! object) {
		return target;
	}

	for (let key in object) {
		let src = object[key],
			type = $type(src);

		if (deep && type == 'object') {
			let len = _set.length,
				i = 0,
				val;

			for (; i < len; i++) {
				if (_set[i] === src) {
					val = src;
					break;
				}
			}

			if (val) {
				target[key] = val;
			} else {
				_set.push(src);
				target[key] = _extend(target[key] || {}, src, deep, _set);
			}
		} else if (src !== undefined) {
			target[key] = type == 'array' ? src.slice(0) : src;
		}
	}

	return target;
}

/**
 * Compare two objects for equality
 *
 * @private
 * @param {Object} a
 * @param {Object} b
 * @returns {boolean}
 */
function _objEquals(a, b) {
	let aKeys = Object.keys(a);

	return _arrEquals(aKeys.sort(), Object.keys(b).sort()) &&
		aKeys.every(function(i) {
			return _equals(a[i], b[i]);
		});
}

/**
 * Clone value to a new instance
 *
 * @private
 * @param {*} val
 * @returns {*}
 */
export function $copy(val) {
	return _copy(val);
}

/**
 * Compare two values for strict equality
 *
 * @param {*} a
 * @param {*} b
 * @returns {boolean}
 */
export function $equals(a, b) {
	return _equals(a, b);
}

/**
 * Extend target object with source object(s)
 *
 * @param {(boolean|Object)} deep - extend nested properties else target object
 * @param {Object} [obj] - target object
 * @param {...Object} [obj] - merged objects
 * @returns {Object}
 */
export function $extend(deep) {
	let bool = typeof deep == 'boolean',
		args = _slice.call(arguments).slice(bool ? 1 : 0),
		target = args[0] || {};
	deep = bool ? deep : false;

	args.slice(1).forEach(function(source) {
		target = _extend(target, source, deep);
	});

	return target;
}

/**
 * Determine if value is an array
 *
 * @param {*} obj
 * @returns {boolean}
 */
export function $isArray(obj) {
	return Array.isArray(obj);
}

/**
 * Determine if value is a function
 *
 * @param {*} obj
 * @returns {boolean}
 */
export function $isFunction(obj) {
	return $type(obj) == 'function';
}

/**
 * Determine if value is a number (optional loose match)
 *
 * @param {*} obj
 * @param {boolean} [strict]
 * @returns {boolean}
 */
export function $isNumber(obj, strict = true) {
	if (! strict) {
		if (! obj.match(/^\d*\.?\d*$/g)) {
			return false;
		}

		let value = parseFloat(obj);

		// If value = NaN, will not be equal
		return value === value;
	}

	return $type(obj) == 'number';
}

/**
 * Determine if value is an object
 *
 * @param {*} obj
 * @returns {boolean}
 */
export function $isObject(obj) {
	return $type(obj) == 'object';
}

/**
 * Determine if value is a string
 *
 * @param {*} obj
 * @returns {boolean}
 */
export function $isString(obj) {
	return typeof obj == 'string';
}

/**
 * Serialize object
 *
 * @param {Object} obj
 * @returns {string} value
 */
export function $serialize(obj) {
	let arr = [];

	Object.keys(obj || {}).forEach(key => {
		let val = obj[key];
		key = encodeURIComponent(key);

		if (typeof val != 'object') {
			arr.push(key + '=' + encodeURIComponent(val));
		} else if (Array.isArray(val)) {
			val.forEach(function(el) {
				arr.push(key + '[]=' + encodeURIComponent(el));
			});
		}
	});

	return arr.length ? arr.join('&').replace(/%20/g, '+') : '';
}

/**
 * Cast value to array if it isn't one
 *
 * @param {*} val
 * @returns {Array} value
 */
export function $toArray(val) {
	return val !== undefined ? (Array.isArray(val) ? val : [val]) : [];
}

/**
 * Determine the JavaScript type of an object
 *
 * @param {*} obj
 * @returns string
 */
export function $type(obj) {
	return obj === undefined ? 'undefined' :
		Object.prototype.toString.call(obj)
			.replace(/^\[object (.+)]$/, '$1')
			.toLowerCase();
}

/**
 * Convert serialized string back into an object
 *
 * @param {string} str
 * @returns {Object} value
 */
export function $unserialize(str) {
	let obj = {};

	decodeURIComponent(str)
		.replace(/^\?/, '')
		.split('&').forEach(function(el) {
		let split = el.split('='),
			key = split[0].replace('[]', ''),
			val = (split[1] || '').replace(/\+/g, ' ') || '';

		if (obj.hasOwnProperty(key)) {
			obj[key] = $toArray(obj[key]);
			obj[key].push(_castString(val));
		} else {
			obj[key] = _castString(val);
		}
	});

	return obj;
}<|endoftext|>


<commit_before>/**
 * @fileoverview A libary for working with SharePoint lists via the SharePoint
 * REST API. Supports reading, updating and deleting list items, as well as a
 * few extra functions such as getting a list of all columns in a SP list.
 */

/**
 * Initializes a new instance of the SpRestApi class, which contains methods
 * for calling the SharePoint REST API.
 * @class SpRestApi
 * @typedef {Object} SpRestApi
 * @constructor
 * @param {SpRestApiOptions} [options] - The optional settings to override the
 *      defaults.
 */
var SpRestApi = function (options) {
    /**
     * The default options that will be used unless overridden.
     * @type {SpRestApiOptions}
     */
    this.defaultOptions = {
        onsuccess: console.log,
        onerror: console.log,
        listTitle: '',
        maxItems: 100,
        recursiveFetch: true,
        verbosity: SpRestApi.Verbosity.VERBOSE,
        siteUrl: _spPageContextInfo ? _spPageContextInfo.webAbsoluteUrl : '',
        token: document.getElementById("__REQUESTDIGEST") ?
            document.getElementById("__REQUESTDIGEST").value : '',
        urls: {
            list: '/_api/web/lists/getbytitle(\'{0}\')/items',
            item: '/_api/web/lists/getbytitle(\'{0}\')/items({1})',
        },
    };

    this.options = $.extend(this.defaultOptions, options);
};

/**
 * Determines the amount of metadata in the response JSON from server.
 * For most cases, 'COMPACT' should be enough.
 * @readonly
 * @enum {string}
 * @typedef Verbosity
 */
SpRestApi.Verbosity = {
    /** Values are placed in `.value`. Less metadata.
        Not supported in SP 2013 and earlier. */
    COMPACT: 'application/json;odata=nometadata',

    /** Value are placed in `.value`. Moderate metadata.
        Not supported in SP 2013 and earlier. */
    MINIMAL: 'application/json;odata=minimalmetadata',

    /** Values are placed in `.d.results`. More metadata. */
    VERBOSE: 'application/json;odata=verbose',
};

/**
 * @typedef {Object} SpRestApiOptions - The options passed to methods
 *      inside the SpRestApi class.
 * @property {Array.<SpRestApiFilterCriterion>} filters - The filters to be
 *      used in filtering the list items using the $filter parameter.
 * @property {string} [token] - The SharePoint's request digest, a token that
 *      it is required for every API call. Required only if using outside a
 *      SharePoint page, otherwise it is obtained automatically from DOM.
 * @property {Function} [onsuccess] - The callback function for successful
 *      requests to SharePoint REST API.
 * @property {Function} [onerror] - The callback function for failed requests
 *      to SharePoint REST API.
 * @property {string} [listTitle] - The display name of the SharePoint list.
 * @property {number} [maxItems] - The maximum number of items to be returned
 *      from the list. If `recursiveFetch` is set to true, this is the
 *      maximum number of items to fetch on each request to server. If not
 *      not specified, defaults to SharePoint's limit of 100. Maximum is
 *      5000 due to SharePoint limitations.
 * @property {boolean} [recursiveFetch] - Fetch all items from the list by
 *      repeatedly making server requests until all list items are fetched.
 *      This is to overcome SharePoint's limitation of maximum 5000 items
 *      per call.
 * @property {Verbosity} [verbosity] - The amount of metadata to be returned
 *      in the JSON response from server. Use the SpRestApi.Verbosity enum.
 * @property {string} [siteUrl] - The SharePoint site URL which is usually
 *      obtained from the _spPageContextInfo.webAbsoluteUrl. Required if using
 *      this library outside of a SharePoint page.
 * @property {Array.<string>} [urls] - The URLs of various API calls, e.g. to
 *      get a list item, all items in a list etc.
 */
/**
 * Stores this instance's options.
 * @type {SpRestApiOptions}
 */
SpRestApi.prototype.options = {};

/**
 * Array of cached SharePoint list items. Stores the data during the recursive
 * fetch (options.recursiveFetch = true), and is cleared immediately when the
 * recursive fetch completes.
 * @type {Array.<Object>}
 */
SpRestApi.prototype.cachedListItems = [];

/**
 * Sets the list title (list display name) of this SpRestApi instance.
 * The .lists() must be called before any call to other list-related
 * methods.
 * @param {string} listTitle - The display name of the SharePoint list.
 * @returns {SpRestApi} Returns the instance of this SpRestApi object.
 */
SpRestApi.prototype.lists = function (listTitle) {
    this.options.listTitle = listTitle;
    return this;
};

/**
 * Sets the SpRestApiOptions. If not called, before the request to server,
 * the default options will be used.
 * @param {SpRestApiOptions} options - The partial SpRestApiOptions object,
 *      where each field will override a default setting in `.defaultOptions`.
 * @returns {SpRestApi} This SpRestApi instance.
 */
SpRestApi.prototype.config = function (options) {
    // Merge the specified options with the default options
    this.options = $.extend(this.defaultOptions, options);
    return this;
};

/**
 * Adds the $top= string to the specified URL, to limit the max number of
 * items in the response.
 * @param {string} url - the URL to add the $top string to.
 * @returns {string} The URL with the added "?"/"&" character and the $top=
 *      URL parameter.
 */
SpRestApi.prototype.addMaxItems = function (url) {
    // Add '?' or '&' to URL query string
    url += url.includes('?') ? '&' : '?';

    return url + "$top=" + this.options.maxItems;
};

/**
 * Returns all items from a list, or all items up to the SharePoint limit
 * or the limit specified in the options.
 */
SpRestApi.prototype.getAllItems = function () {
    var url = this.generateGetAllListItemsUrl();

    var onsuccess;
    if (this.options.recursiveFetch) {
        // Continue fetching items recursively until we load the entire list:
        onsuccess = this.continueRecursiveFetch;
    } else {
        onsuccess = this.options.onsuccess;
    }

    this.loadUrl(url, 'GET', onsuccess, this.options.onerror);
};

/**
 * Generates the URL for fetching all items from a list. Uses in getAllItems()
 * and in getAllItemsFromSubfolder().
 * @returns {string} The SP API URL for fetching all items from a list.
 */
SpRestApi.prototype.generateGetAllListItemsUrl = function () {
    var url = this.options.siteUrl +
        this.options.urls.list.format(this.options.listTitle);
    url = this.addMaxItems(url);
    return url;
};

/**
 * Generates the URL for getting or deleting a single list item.
 * @param {number} itemId - The list item ID of the item to be deleted.
 * @returns {string} An URL ending with e.g. /Lists('MyList')/Items(761)
 */
SpRestApi.prototype.generateSingleListItemUrl = function (itemId) {
    return this.options.siteUrl +
        this.options.urls.item.format(this.options.listTitle, itemId);
}

/**
 * Returns all list items from a subfolder of a SharePoint list. Uses a hacky
 * way - by matching a substring of the FileRef, to avoid relying on CAML.
 * @param {string} subfolderName - The display name of the subfolder in a list.
 *      Must not include any slashes.
 */
SpRestApi.prototype.getAllItemsFromListSubfolder = function (subfolderName) {
    // The idea here is that if the FileRef property of a list item is
    // like this - /sites/mysite/Lists/My List/My Subfolder/123_.000 -
    // then we can filter the list by FileRef containing the
    // string 'Lists/My List/My Subfolder'.
    var fileref = 'Lists/' +
        this.options.listTitle + '/' + subfolderName + '/';

    var url = this.generateGetAllListItemsUrl();
    url += '&$filter=substringof(\'' + fileref + '\', FileRef)';

    var onsuccess;
    if (this.options.recursiveFetch) {
        // Continue fetching items recursively until we load the entire list:
        onsuccess = this.continueRecursiveFetch;
    } else {
        onsuccess = this.options.onsuccess;
    }

    this.loadUrl(url, 'GET', onsuccess, this.options.onerror);
};

/**
 * Keeps loading data recursively until all list items are obtained. This is
 * to overcome SharePoint's limitation on the number of list items per query.
 */
SpRestApi.prototype.continueRecursiveFetch = function (data) {
    var newData;
    var nextUrl;

    // Depending on the Verbosity setting, the data will be returned in
    // significantly different formats (inside data.d.results or data.value).
    if (this.options.verbosity === SpRestApi.Verbosity.VERBOSE) {
        newData = data.d.results;
        nextUrl = data.d.__next;
    } else {
        newData = data.value;
        nextUrl = data['odata.nextLink']; // dot in property name
    }

    this.cachedListItems = this.cachedListItems.concat(newData);

    if (nextUrl) {
        // While next URL is not empty, keep loading recursively:
        this.loadUrl(nextUrl, 'GET',
            this.continueRecursiveFetch, this.options.onerror);
    } else {
        // Load complete - generate a structure similar to server response,
        // and call the callback.
        var entireData;
        if (this.options.verbosity === SpRestApi.Verbosity.VERBOSE) {
            entireData = {
                d: {
                    results: this.cachedListItems
                }
            };
        } else {
            entireData = {
                value: this.cachedListItems
            };
        }

        this.options.success(entireData);
    }
};

/**
 * Returns a single item from a list.
 * @param {number} itemId - The SharePoint list item ID of the item we need to
 *      fetch.
 */
SpRestApi.prototype.getItem = function (itemId) {
    if (!itemId) { throw "The list item ID must not be empty."; }

    var url = this.options.siteUrl +
        this.options.urls.item.format(this.options.listTitle, itemId);
    this.loadUrl(url, 'GET', this.options.onsuccess, this.options.onerror);
};

/**
 * Deletes a single item from a SharePoint list.
 * @type {number} itemId - The SharePoint list item ID of the item to be
 *      deleted.
 */
SpRestApi.prototype.deleteItem = function (itemId) {
    var url =
};

/**
 * A generic function to call any URL of the SharePoint REST API. Usually
 * there is no need to call this method directly.
 * @param {string} url - The URL of the SharePoint REST API to be queried. Will
 *      not be modified by this method.
 * @param {string} method - HTTP method for this request, e.g. 'GET', 'POST',
 *      'DELETE'.
 * @param {Function} success - Callback for successfull REST API call.
 * @param {Function} error - Callback for failed REST API call.
 */
SpRestApi.prototype.loadUrl = function (url, method, success, error) {
    // Preserve `this` containing the current instance of SpRestApi.
    var $ajax = $.proxy($.ajax, this);

    // These headers are common for all requests
    var headers = {
        "Accept": this.options.verbosity,
        "X-RequestDigest": this.options.token,
    };

    // For the DELETE method, we actually send the request as POST
    if (method === 'DELETE') {
        headers['IF-MATCH'] = '*';
        headers["X-HTTP-METHOD"] = 'DELETE';
        method = 'POST';
    }

    $ajax({
        url: url,
        type: method,
        cache: false,
        contentType: this.options.verbosity,
        headers: headers,
        success: function (data) {
            success(data);
        },
        error: function (data) {
            error(data);
        },
    });
};

/**
 * Sends a request to SharePoint to get the SharePoint authorization token,
 * which is required for all data requests. This is necessary in two cases:
 * - We are loading this script from a non-SharePoint page (e.g. normal HTML);
 * - We are refreshing the token (which expires after 30 minutes).
 * @param {Function} callback - The callback to run after the authorization
 *      token is received successfully.
 */
SpRestApi.prototype.refreshDigest = function (callback) {
    var url = this.siteUrl + '/_api/contextinfo';

    // If request succeeded:
    var success = function (data) {
        if (data && data.d && data.d.GetContextWebInformation &&
            data.d.GetContextWebInformation.FormDigestValue) {
            // If Verbosity = odata=verbose:
            this.options = data.d.GetContextWebInformation.FormDigestValue;

        } else if (data && data.value && data.value.GetContextWebInformation &&
            data.value.GetContextWebInformation.FormDigestValue) { // TODO FIXME need to verify this path is correct in SP online
            // If Verbosity = odata=nometadata, odata=minimalmetadata:
            this.options = data.value.GetContextWebInformation.FormDigestValue;
        } else {
            throw 'Unable to obtain SharePoint authorization token.';
        }

        if (typeof callback === 'function') {
            callback();
        }
    };

    // If request failed:
    var error = function () {
        throw 'Unable to obtain SharePoint authorization token';
    };

    this.loadUrl(url, 'POST', success, error);
};

/* Polyfills
-----------------------------*/

if (!String.prototype.format) {
    /**
    * Replaces symbols like {0}, {1} in a string with the values from
    * the arguments.
    * @returns {string} The string with the replaced placeholders.
    */
    String.prototype.format = function () {
        var args = arguments;
        return this.replace(/{(\d+)}/g, function (match, number) {
            return typeof args[number] !== 'undefined'
                ? args[number]
                : match
                ;
        });
    };
}

if (!String.prototype.includes) {
    String.prototype.includes = function (search, start) {
        'use strict';
        if (typeof start !== 'number') {
            start = 0;
        }

        if (start + search.length > this.length) {
            return false;
        } else {
            return this.indexOf(search, start) !== -1;
        }
    };
}<commit_msg>implement delete item function<commit_after>/**
 * @fileoverview A libary for working with SharePoint lists via the SharePoint
 * REST API. Supports reading, updating and deleting list items, as well as a
 * few extra functions such as getting a list of all columns in a SP list.
 */

/**
 * Initializes a new instance of the SpRestApi class, which contains methods
 * for calling the SharePoint REST API.
 * @class SpRestApi
 * @typedef {Object} SpRestApi
 * @constructor
 * @param {SpRestApiOptions} [options] - The optional settings to override the
 *      defaults.
 */
var SpRestApi = function (options) {
    /**
     * The default options that will be used unless overridden.
     * @type {SpRestApiOptions}
     */
    this.defaultOptions = {
        onsuccess: console.log,
        onerror: console.log,
        listTitle: '',
        maxItems: 100,
        recursiveFetch: true,
        verbosity: SpRestApi.Verbosity.VERBOSE,
        siteUrl: _spPageContextInfo ? _spPageContextInfo.webAbsoluteUrl : '',
        token: document.getElementById("__REQUESTDIGEST") ?
            document.getElementById("__REQUESTDIGEST").value : '',
        urls: {
            list: '/_api/web/lists/getbytitle(\'{0}\')/items',
            item: '/_api/web/lists/getbytitle(\'{0}\')/items({1})',
        },
    };

    this.options = $.extend(this.defaultOptions, options);
};

/**
 * Determines the amount of metadata in the response JSON from server.
 * For most cases, 'COMPACT' should be enough.
 * @readonly
 * @enum {string}
 * @typedef Verbosity
 */
SpRestApi.Verbosity = {
    /** Values are placed in `.value`. Less metadata.
        Not supported in SP 2013 and earlier. */
    COMPACT: 'application/json;odata=nometadata',

    /** Value are placed in `.value`. Moderate metadata.
        Not supported in SP 2013 and earlier. */
    MINIMAL: 'application/json;odata=minimalmetadata',

    /** Values are placed in `.d.results`. More metadata. */
    VERBOSE: 'application/json;odata=verbose',
};

/**
 * @typedef {Object} SpRestApiOptions - The options passed to methods
 *      inside the SpRestApi class.
 * @property {Array.<SpRestApiFilterCriterion>} filters - The filters to be
 *      used in filtering the list items using the $filter parameter.
 * @property {string} [token] - The SharePoint's request digest, a token that
 *      it is required for every API call. Required only if using outside a
 *      SharePoint page, otherwise it is obtained automatically from DOM.
 * @property {Function} [onsuccess] - The callback function for successful
 *      requests to SharePoint REST API.
 * @property {Function} [onerror] - The callback function for failed requests
 *      to SharePoint REST API.
 * @property {string} [listTitle] - The display name of the SharePoint list.
 * @property {number} [maxItems] - The maximum number of items to be returned
 *      from the list. If `recursiveFetch` is set to true, this is the
 *      maximum number of items to fetch on each request to server. If not
 *      not specified, defaults to SharePoint's limit of 100. Maximum is
 *      5000 due to SharePoint limitations.
 * @property {boolean} [recursiveFetch] - Fetch all items from the list by
 *      repeatedly making server requests until all list items are fetched.
 *      This is to overcome SharePoint's limitation of maximum 5000 items
 *      per call.
 * @property {Verbosity} [verbosity] - The amount of metadata to be returned
 *      in the JSON response from server. Use the SpRestApi.Verbosity enum.
 * @property {string} [siteUrl] - The SharePoint site URL which is usually
 *      obtained from the _spPageContextInfo.webAbsoluteUrl. Required if using
 *      this library outside of a SharePoint page.
 * @property {Array.<string>} [urls] - The URLs of various API calls, e.g. to
 *      get a list item, all items in a list etc.
 */
/**
 * Stores this instance's options.
 * @type {SpRestApiOptions}
 */
SpRestApi.prototype.options = {};

/**
 * Array of cached SharePoint list items. Stores the data during the recursive
 * fetch (options.recursiveFetch = true), and is cleared immediately when the
 * recursive fetch completes.
 * @type {Array.<Object>}
 */
SpRestApi.prototype.cachedListItems = [];

/**
 * Sets the list title (list display name) of this SpRestApi instance.
 * The .lists() must be called before any call to other list-related
 * methods.
 * @param {string} listTitle - The display name of the SharePoint list.
 * @returns {SpRestApi} Returns the instance of this SpRestApi object.
 */
SpRestApi.prototype.lists = function (listTitle) {
    this.options.listTitle = listTitle;
    return this;
};

/**
 * Sets the SpRestApiOptions. If not called, before the request to server,
 * the default options will be used.
 * @param {SpRestApiOptions} options - The partial SpRestApiOptions object,
 *      where each field will override a default setting in `.defaultOptions`.
 * @returns {SpRestApi} This SpRestApi instance.
 */
SpRestApi.prototype.config = function (options) {
    // Merge the specified options with the default options
    this.options = $.extend(this.defaultOptions, options);
    return this;
};

/**
 * Adds the $top= string to the specified URL, to limit the max number of
 * items in the response.
 * @param {string} url - the URL to add the $top string to.
 * @returns {string} The URL with the added "?"/"&" character and the $top=
 *      URL parameter.
 */
SpRestApi.prototype.addMaxItems = function (url) {
    // Add '?' or '&' to URL query string
    url += url.includes('?') ? '&' : '?';

    return url + "$top=" + this.options.maxItems;
};

/**
 * Returns all items from a list, or all items up to the SharePoint limit
 * or the limit specified in the options.
 */
SpRestApi.prototype.getAllItems = function () {
    var url = this.generateGetAllListItemsUrl();

    var onsuccess;
    if (this.options.recursiveFetch) {
        // Continue fetching items recursively until we load the entire list:
        onsuccess = this.continueRecursiveFetch;
    } else {
        onsuccess = this.options.onsuccess;
    }

    this.loadUrl(url, 'GET', onsuccess, this.options.onerror);
};

/**
 * Generates the URL for fetching all items from a list. Uses in getAllItems()
 * and in getAllItemsFromSubfolder().
 * @returns {string} The SP API URL for fetching all items from a list.
 */
SpRestApi.prototype.generateGetAllListItemsUrl = function () {
    var url = this.options.siteUrl +
        this.options.urls.list.format(this.options.listTitle);
    url = this.addMaxItems(url);
    return url;
};

/**
 * Generates the URL for getting or deleting a single list item.
 * @param {number} itemId - The list item ID of the item to be deleted.
 * @returns {string} An URL ending with e.g. /Lists('MyList')/Items(761)
 */
SpRestApi.prototype.generateSingleListItemUrl = function (itemId) {
    return this.options.siteUrl +
        this.options.urls.item.format(this.options.listTitle, itemId);
}

/**
 * Returns all list items from a subfolder of a SharePoint list. Uses a hacky
 * way - by matching a substring of the FileRef, to avoid relying on CAML.
 * @param {string} subfolderName - The display name of the subfolder in a list.
 *      Must not include any slashes.
 */
SpRestApi.prototype.getAllItemsFromListSubfolder = function (subfolderName) {
    // The idea here is that if the FileRef property of a list item is
    // like this - /sites/mysite/Lists/My List/My Subfolder/123_.000 -
    // then we can filter the list by FileRef containing the
    // string 'Lists/My List/My Subfolder'.
    var fileref = 'Lists/' +
        this.options.listTitle + '/' + subfolderName + '/';

    var url = this.generateGetAllListItemsUrl();
    url += '&$filter=substringof(\'' + fileref + '\', FileRef)';

    var onsuccess;
    if (this.options.recursiveFetch) {
        // Continue fetching items recursively until we load the entire list:
        onsuccess = this.continueRecursiveFetch;
    } else {
        onsuccess = this.options.onsuccess;
    }

    this.loadUrl(url, 'GET', onsuccess, this.options.onerror);
};

/**
 * Keeps loading data recursively until all list items are obtained. This is
 * to overcome SharePoint's limitation on the number of list items per query.
 */
SpRestApi.prototype.continueRecursiveFetch = function (data) {
    var newData;
    var nextUrl;

    // Depending on the Verbosity setting, the data will be returned in
    // significantly different formats (inside data.d.results or data.value).
    if (this.options.verbosity === SpRestApi.Verbosity.VERBOSE) {
        newData = data.d.results;
        nextUrl = data.d.__next;
    } else {
        newData = data.value;
        nextUrl = data['odata.nextLink']; // dot in property name
    }

    this.cachedListItems = this.cachedListItems.concat(newData);

    if (nextUrl) {
        // While next URL is not empty, keep loading recursively:
        this.loadUrl(nextUrl, 'GET',
            this.continueRecursiveFetch, this.options.onerror);
    } else {
        // Load complete - generate a structure similar to server response,
        // and call the callback.
        var entireData;
        if (this.options.verbosity === SpRestApi.Verbosity.VERBOSE) {
            entireData = {
                d: {
                    results: this.cachedListItems
                }
            };
        } else {
            entireData = {
                value: this.cachedListItems
            };
        }

        this.options.success(entireData);
    }
};

/**
 * Returns a single item from a list.
 * @param {number} itemId - The SharePoint list item ID of the item we need to
 *      fetch.
 */
SpRestApi.prototype.getItem = function (itemId) {
    if (!itemId) { throw "The list item ID must not be empty."; }

    var url = this.options.siteUrl +
        this.options.urls.item.format(this.options.listTitle, itemId);
    this.loadUrl(url, 'GET', this.options.onsuccess, this.options.onerror);
};

/**
 * Deletes a single item from a SharePoint list.
 * @type {number} itemId - The SharePoint list item ID of the item to be
 *      deleted.
 */
SpRestApi.prototype.deleteItem = function (itemId) {
    var url = this.generateSingleListItemUrl(itemId);
    this.loadUrl(url, 'DELETE', this.options.onsuccess, this.options.onerror);
};

/**
 * A generic function to call any URL of the SharePoint REST API. Usually
 * there is no need to call this method directly.
 * @param {string} url - The URL of the SharePoint REST API to be queried. Will
 *      not be modified by this method.
 * @param {string} method - HTTP method for this request, e.g. 'GET', 'POST',
 *      'DELETE'.
 * @param {Function} success - Callback for successfull REST API call.
 * @param {Function} error - Callback for failed REST API call.
 */
SpRestApi.prototype.loadUrl = function (url, method, success, error) {
    // Preserve `this` containing the current instance of SpRestApi.
    var $ajax = $.proxy($.ajax, this);

    // These headers are common for all requests
    var headers = {
        "Accept": this.options.verbosity,
        "X-RequestDigest": this.options.token,
    };

    // For the DELETE method, we actually send the request as POST
    if (method === 'DELETE') {
        headers['IF-MATCH'] = '*';
        headers["X-HTTP-METHOD"] = 'DELETE';
        method = 'POST';
    }

    $ajax({
        url: url,
        type: method,
        cache: false,
        contentType: this.options.verbosity,
        headers: headers,
        success: function (data) {
            success(data);
        },
        error: function (data) {
            error(data);
        },
    });
};

/**
 * Sends a request to SharePoint to get the SharePoint authorization token,
 * which is required for all data requests. This is necessary in two cases:
 * - We are loading this script from a non-SharePoint page (e.g. normal HTML);
 * - We are refreshing the token (which expires after 30 minutes).
 * @param {Function} callback - The callback to run after the authorization
 *      token is received successfully.
 */
SpRestApi.prototype.refreshDigest = function (callback) {
    var url = this.siteUrl + '/_api/contextinfo';

    // If request succeeded:
    var success = function (data) {
        if (data && data.d && data.d.GetContextWebInformation &&
            data.d.GetContextWebInformation.FormDigestValue) {
            // If Verbosity = odata=verbose:
            this.options = data.d.GetContextWebInformation.FormDigestValue;

        } else if (data && data.value && data.value.GetContextWebInformation &&
            data.value.GetContextWebInformation.FormDigestValue) { // TODO FIXME need to verify this path is correct in SP online
            // If Verbosity = odata=nometadata, odata=minimalmetadata:
            this.options = data.value.GetContextWebInformation.FormDigestValue;
        } else {
            throw 'Unable to obtain SharePoint authorization token.';
        }

        if (typeof callback === 'function') {
            callback();
        }
    };

    // If request failed:
    var error = function () {
        throw 'Unable to obtain SharePoint authorization token';
    };

    this.loadUrl(url, 'POST', success, error);
};

/* Polyfills
-----------------------------*/

if (!String.prototype.format) {
    /**
    * Replaces symbols like {0}, {1} in a string with the values from
    * the arguments.
    * @returns {string} The string with the replaced placeholders.
    */
    String.prototype.format = function () {
        var args = arguments;
        return this.replace(/{(\d+)}/g, function (match, number) {
            return typeof args[number] !== 'undefined'
                ? args[number]
                : match
                ;
        });
    };
}

if (!String.prototype.includes) {
    String.prototype.includes = function (search, start) {
        'use strict';
        if (typeof start !== 'number') {
            start = 0;
        }

        if (start + search.length > this.length) {
            return false;
        } else {
            return this.indexOf(search, start) !== -1;
        }
    };
}<|endoftext|>


<commit_before>/*
  Expand - Short Page Fix
  Version: 1.4
  Developer: Jonathan Chute
  Year: 2018
*/
(function( $ ) {
    $.fn.expand = function(options) {

        var settings = $.extend( {
            'sections': 'div',
            'last':   '',
            'wrapper': 'body',
            'after': false,
        }, options );

        var mainObj = $(this);

        if ( mainObj[0] === undefined ) {
            return;
        }

        settings.sections = $(settings.wrapper).children( settings.sections );

        setHeight(); setInterval( setHeight, 400 );

        function setHeight() {

            var height = $(window).height();

            height -= subtract( 'html', true );
            height -= subtract( 'body', true );
            height -= ( ! compare( $('body'), $(settings.wrapper) ) ) ? subtract( $(settings.wrapper), true ) : 0;
            height -= subtract( mainObj, false );

            settings.sections.not( mainObj ).each( function() {

                var position = $(this).css( 'position' );

                if ( position == 'absolute' || position == 'fixed' ) {

                    return true;

                }

                height -= $(this).outerHeight( true );

                if ( settings.last != '' ) {

                    if ( $(this).is( settings.last ) ) {

                        return false;

                    }

                }

            } );

            $(mainObj).css( 'min-height', height );

            if ( settings.after != false ) {

                settings.after();

            }
        }

        function subtract( object, padding ) {

            var total = 0;

            total += parseInt( $(object).css( 'margin-top' ) );
            total += parseInt( $(object).css( 'margin-bottom' ) );
            total += parseInt( $(object).css( 'border-top-width' ) );
            total += parseInt( $(object).css( 'border-bottom-width' ) );

            if ( padding ) {

                total += parseInt($(object).css('padding-top'));
                total += parseInt($(object).css('padding-bottom'));

            }

            return total;

        }

        function compare( object1, object2 ) {

            if ( object1.length !== object2.length ) {

                return false;

            }

            for ( var i = 0; i < object1.length; i++ ) {

                if ( object1[i] !== object2[i] ) {
                    return false;
                }

            }

            return true;

        }

    };
})( jQuery );
<commit_msg>Update expand.js<commit_after>/*
  Expand - Short Page Fix
  Version: 1.4
  Developer: Jonathan Chute
  Year: 2018
*/
(function( $ ) {
    $.fn.expand = function(options) {

        var settings = $.extend( {
            'sections': '',
            'last':   '',
            'wrapper': 'body',
            'after': false,
        }, options );

        var mainObj = $(this);

        if ( mainObj[0] === undefined ) {
            return;
        }

        settings.sections = $(settings.wrapper).children( settings.sections );

        setHeight(); setInterval( setHeight, 400 );

        function setHeight() {

            var height = $(window).height();

            height -= subtract( 'html', true );
            height -= subtract( 'body', true );
            height -= ( ! compare( $('body'), $(settings.wrapper) ) ) ? subtract( $(settings.wrapper), true ) : 0;
            height -= subtract( mainObj, false );

            settings.sections.not( mainObj ).each( function() {

                var position = $(this).css( 'position' );

                if ( position == 'absolute' || position == 'fixed' ) {

                    return true;

                }

                height -= $(this).outerHeight( true );

                if ( settings.last != '' ) {

                    if ( $(this).is( settings.last ) ) {

                        return false;

                    }

                }

            } );

            $(mainObj).css( 'min-height', height );

            if ( settings.after != false ) {

                settings.after();

            }
        }

        function subtract( object, padding ) {

            var total = 0;

            total += parseInt( $(object).css( 'margin-top' ) );
            total += parseInt( $(object).css( 'margin-bottom' ) );
            total += parseInt( $(object).css( 'border-top-width' ) );
            total += parseInt( $(object).css( 'border-bottom-width' ) );

            if ( padding ) {

                total += parseInt($(object).css('padding-top'));
                total += parseInt($(object).css('padding-bottom'));

            }

            return total;

        }

        function compare( object1, object2 ) {

            if ( object1.length !== object2.length ) {

                return false;

            }

            for ( var i = 0; i < object1.length; i++ ) {

                if ( object1[i] !== object2[i] ) {
                    return false;
                }

            }

            return true;

        }

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


<commit_before>const Message = require('./../../orm/Message');
const m2e = require('./../lib/message2embed');
const bot = require('./../bot');
/**
 * This method should return the response directly to the channel
 * @param {*string array} params
 * @param {*message} message
 */
async function command(params, message) {
   if (params.length < 1)
        return message.reply("incorrect usage\nSyntax: ~quote <messageID>");
   let m =await Message.where('messageID', params[0]).fetch();
   if (m) {
        console.log(m);
        let ch = bot.channels.get(m.attributes.channel);
        m = await ch.fetchMessage(m.attributes.messageID);
    }
    else
    {
        m = await message.channel.fetchMessage(params[0]);
        if(!m)
            return message.reply("message not available");
    }
    console.log("fetching : "+m.id);
    let attach = m.attachments.first();
    m =m2e(m);
    console.log(m);
    if(attach)
        attach = attach.url;
    message.channel.sendEmbed(m,{file:attach}).catch(console.error);
}
/**
 * description of the command
 */
const description = "quotes a message via id";
/**
 * Define Exports
 */
module.exports = {
    execute: command,
    description: description
};
<commit_msg>add color<commit_after>const Message = require('./../../orm/Message');
const m2e = require('./../lib/message2embed');
const bot = require('./../bot');
/**
 * This method should return the response directly to the channel
 * @param {*string array} params
 * @param {*message} message
 */
async function command(params, message) {
   if (params.length < 1)
        return message.reply("incorrect usage\nSyntax: ~quote <messageID>");
   let m =await Message.where('messageID', params[0]).fetch();
   if (m) {
        console.log(m);
        let ch = bot.channels.get(m.attributes.channel);
        m = await ch.fetchMessage(m.attributes.messageID);
    }
    else
    {
        m = await message.channel.fetchMessage(params[0]);
        if(!m)
            return message.reply("message not available");
    }
    console.log("fetching : "+m.id);
    let attach = m.attachments.first();
    m =m2e(m);
    console.log(m);
    m.setColor((m.author.id === message.author.id)?"#FFFF00":"#00FF00");
    if(attach)
        attach = attach.url;
    message.channel.sendEmbed(m,{file:attach}).catch(console.error);
}
/**
 * description of the command
 */
const description = "quotes a message via id";
/**
 * Define Exports
 */
module.exports = {
    execute: command,
    description: description
};
<|endoftext|>


<commit_before>const Message = require('./../../orm/Message');
const m2e = require('./../lib/message2embed');
const bot = require('./../bot');
/**
 * This method should return the response directly to the channel
 * @param {*string array} params
 * @param {*message} message
 */
async function command(params, message) {
    if (params.length < 1)
        return message.reply("incorrect usage\nSyntax: ~quote <messageID>");
    let m = await Message.where('messageID', params[0]).fetch();
    if (m) {
        console.log(m);
        let ch = bot.channels.get(m.attributes.channel);
        m = await ch.fetchMessage(m.attributes.messageID);
    }
    else if (params[0].startsWith('-'))
    {
        params[0] = parseInt(params[0]);
        m = await message.channel.fetchMessages({limit: params[0]});
        m = m.first();
    }
    else {
        m = await message.channel.fetchMessage(params[0]);
    }
    if (!m)
        return message.reply("message not available");
    console.log("fetching : " + m.id);
    let attach = m.attachments.first();
    let em = m2e(m);
    console.log(em);
    if (attach)
        attach = attach.url;
    message.channel.send((params[1])?(message.author.toString()+": "+params.slice(1).join(" ")):(""), { embed:em , file: attach }).catch(console.error);
}
/**
 * description of the command
 */
const description = "quotes a message via id";
/**
 * Define Exports
 */
module.exports = {
    execute: command,
    description: description
};
<commit_msg>fix quote via index<commit_after>const Message = require('./../../orm/Message');
const m2e = require('./../lib/message2embed');
const bot = require('./../bot');
/**
 * This method should return the response directly to the channel
 * @param {*string array} params
 * @param {*message} message
 */
async function command(params, message) {
    if (params.length < 1)
        return message.reply("incorrect usage\nSyntax: ~quote <messageID>");
    let m = await Message.where('messageID', params[0]).fetch();
    if (m) {
        console.log(m);
        let ch = bot.channels.get(m.attributes.channel);
        m = await ch.fetchMessage(m.attributes.messageID);
    }
    else if (params[0].startsWith('-'))
    {
        params[0] = parseInt(params[0].substring(1));
        m = await message.channel.fetchMessages({limit: params[0]});
        m = m.first();
    }
    else {
        m = await message.channel.fetchMessage(params[0]);
    }
    if (!m)
        return message.reply("message not available");
    console.log("fetching : " + m.id);
    let attach = m.attachments.first();
    let em = m2e(m);
    console.log(em);
    if (attach)
        attach = attach.url;
    message.channel.send((params[1])?(message.author.toString()+": "+params.slice(1).join(" ")):(""), { embed:em , file: attach }).catch(console.error);
}
/**
 * description of the command
 */
const description = "quotes a message via id";
/**
 * Define Exports
 */
module.exports = {
    execute: command,
    description: description
};
<|endoftext|>