{"action":"created","comment":{"url":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/comments/51301353","id":51301353,"diff_hunk":"@@ -0,0 +1,1175 @@\n+/**\n+ * Builds full page project browser\n+ */\n+'use strict';\n+\n+var $ = require('jquery');  // jQuery\n+var m = require('mithril'); // exposes mithril methods, useful for redraw etc.\n+var ProjectOrganizer = require('js/project-organizer');\n+var $osf = require('js/osfHelpers');\n+var LogText = require('js/logTextParser');\n+var AddProject = require('js/addProjectPlugin');\n+var mC = require('js/mithrilComponents');\n+\n+if (!window.fileBrowserCounter) {\n+    window.fileBrowserCounter = 0;\n+}\n+function getUID() {\n+    window.fileBrowserCounter = window.fileBrowserCounter + 1;\n+    return window.fileBrowserCounter;\n+}\n+\n+var xhrconfig = function (xhr) {\n+    xhr.withCredentials = true;\n+    xhr.setRequestHeader('Content-Type', 'application/vnd.api+json');\n+    xhr.setRequestHeader('Accept', 'application/vnd.api+json; ext=bulk');\n+};\n+\n+// Refactor the information needed for filtering rows\n+function _formatDataforPO(item) {\n+    item.kind = 'folder';\n+    item.uid = item.id;\n+    item.name = item.attributes.title;\n+    item.tags = item.attributes.tags.toString();\n+    item.contributors = '';\n+    if (!item.embeds.contributors.data){\n+        console.log(item.embeds.contributors.errors);\n+    } else {\n+        item.embeds.contributors.data.forEach(function(c){\n+            var attr = c.embeds.users.data.attributes;\n+            item.contributors += attr.full_name + ' ' + attr.middle_names + ' ' + attr.given_name + ' ' + attr.family_name + ' ' ;\n+        });\n+    }\n+    item.date = new $osf.FormattableDate(item.attributes.date_modified);\n+    return item;\n+}\n+\n+var LinkObject = function _LinkObject (type, data, label, index) {\n+    if (type === undefined || data === undefined || label === undefined) {\n+        throw new Error('LinkObject expects type, data and label to be defined.');\n+    }\n+    if (index !== undefined && ( typeof index !== 'number' || index <= 0)){\n+        throw new Error('Index needs to be a number starting from 0; instead \"' + index + '\" was given.');\n+    }\n+    var self = this;\n+    self.id = getUID();\n+    self.type = type;\n+    self.data = data;\n+    self.label = label;\n+    self.index = index;  // For breadcrumbs to cut off when clicking parent level\n+    self.generateLink = function () {\n+        if (self.type === 'collection'){\n+                return $osf.apiV2Url(self.data.path, {\n+                        query : self.data.query\n+                    }\n+                );\n+        }\n+        else if (self.type === 'tag') {\n+            return $osf.apiV2Url('nodes/', { query : {'filter[tags]' : self.data.tag , 'related_counts' : true, 'embed' : 'contributors'}});\n+        }\n+        else if (self.type === 'name') {\n+            return $osf.apiV2Url('users/' + self.data.id + '/nodes/', { query : {'related_counts' : true, 'embed' : 'contributors' }});\n+        }\n+        else if (self.type === 'node') {\n+            return $osf.apiV2Url('nodes/' + self.data.uid + '/children/', { query : { 'related_counts' : true, 'embed' : 'contributors' }});\n+        }\n+        // If nothing\n+        throw new Error('Link could not be generated from linkObject data');\n+    };\n+    self.link = self.generateLink();\n+};\n+\n+\n+function _makeTree (flatData) {\n+    var root = {id:0, children: [], data : {} };\n+    var node_list = { 0 : root};\n+    var parentID;\n+    for (var i = 0; i < flatData.length; i++) {\n+        var n = _formatDataforPO(flatData[i]);\n+        if (!node_list[n.id]) { // If this node is not in the object list, add it\n+            node_list[n.id] = n;\n+            node_list[n.id].children = [];\n+        } else { // If this node is in object list it's likely because it was created as a parent so fill out the rest of the information\n+            n.children = node_list[n.id].children;\n+            node_list[n.id] = n;\n+        }\n+\n+        if (n.relationships.parent){\n+            parentID = n.relationships.parent.links.related.href.split('/')[5]; // Find where parent id string can be extracted\n+        } else {\n+            parentID = null;\n+        }\n+        if(parentID && !n.attributes.registration ) {\n+            if(!node_list[parentID]){\n+                node_list[parentID] = { children : [] };\n+            }\n+            node_list[parentID].children.push(node_list[n.id]);\n+\n+        } else {\n+            node_list[0].children.push(node_list[n.id]);\n+        }\n+    }\n+    return root.children;\n+}\n+\n+/**\n+ * Returns the object to send to the API to end a node_link to collection\n+ * @param id {String} unique id of the node like 'ez8f3'\n+ * @returns {{data: {type: string, relationships: {nodes: {data: {type: string, id: *}}}}}}\n+ */\n+function buildCollectionNodeData (id) {\n+    return {\n+        'data' : {\n+            'type': 'node_links',\n+            'relationships': {\n+                'nodes': {\n+                    'data': {\n+                        'type': 'nodes',\n+                        'id': id\n+                    }\n+                }\n+            }\n+        }\n+    };\n+}\n+\n+/**\n+ * Initialize File Browser. Prepeares an option object within FileBrowser\n+ * @constructor\n+ */\n+var FileBrowser = {\n+    controller : function (options) {\n+        var self = this;\n+        self.wrapperSelector = options.wrapperSelector;  // For encapsulating each implementation of this component in multiple use\n+        self.currentLink = ''; // Save the link to compare if a new link is being requested and avoid multiple calls\n+        self.reload = m.prop(false); // Gets set to true when treebeard link changes and it needs to be redrawn\n+        self.nonLoadTemplate = m.prop(m('.fb-non-load-template.m-md.p-md.osf-box', 'Loading...')); // Template for when data is not available or error happens\n+\n+        // VIEW STATES\n+        self.showInfo = m.prop(true); // Show the info panel\n+        self.showSidebar = m.prop(true); // Show the links with collections etc. used in narrow views\n+        self.refreshView = m.prop(true); // Internal loading indicator\n+        self.allProjectsLoaded = m.prop(false);\n+        self.allProjects = m.prop([]);\n+        self.loadingNodePages = false; // Since API returns pages of items, this state shows whether filebrowser is still loading the next pages.\n+        self.loadingAllNodes = false; // True if we are loading all nodes\n+\n+        // Load 'All my Projects' and 'All my Registrations'\n+        self.systemCollections = [\n+            new LinkObject('collection', { path : 'users/me/nodes/', query : { 'related_counts' : true, 'page[size]'  : 12, 'embed' : 'contributors' }, systemCollection : 'nodes'}, 'All My Projects'),\n+            new LinkObject('collection', { path : 'users/me/registrations/', query : { 'related_counts' : true, 'page[size]'  : 12, 'embed' : 'contributors'}, systemCollection : 'registrations'}, 'All My Registrations')\n+        ];\n+        // Initial Breadcrumb for All my projects\n+        self.breadcrumbs = m.prop([\n+            new LinkObject('collection', { path : 'users/me/nodes/', query : { 'related_counts' : true, 'embed' : 'contributors' }, systemCollection : 'nodes'}, 'All My Projects')\n+        ]);\n+        // Calculate name filters\n+        self.nameFilters = [];\n+        // Calculate tag filters\n+        self.tagFilters = [];\n+\n+        // Placeholder for node data\n+        self.data = m.prop([]);\n+\n+        // Activity Logs\n+        self.activityLogs = m.prop();\n+        self.showMoreActivityLogs = m.prop(null);\n+        self.getLogs = function _getLogs (nodeId, link, addToExistingList) {\n+            var url = link || $osf.apiV2Url('nodes/' + nodeId + '/logs/', { query : { 'page[size]' : 6, 'embed' : ['nodes', 'user', 'linked_node', 'template_node']}});\n+            var promise = m.request({method : 'GET', url : url, config : xhrconfig});\n+            self.activityLogs([]); // Empty old logs first;\n+            self.showMoreActivityLogs(null);\n+            promise.then(function _getLogsThen (result){\n+                result.data.map(function(log){\n+                    log.attributes.formattableDate = new $osf.FormattableDate(log.attributes.date);\n+                    if(addToExistingList){\n+                        self.activityLogs().push(log);\n+                    }\n+                });\n+                if(!addToExistingList){\n+                    self.activityLogs(result.data);  // Set activity log data\n+                }\n+                self.showMoreActivityLogs(result.links.next); // Set view for show more button\n+            });\n+            return promise;\n+        };\n+        // separate concerns, wrap getlogs here to get logs for the selected item\n+        self.getCurrentLogs = function _getCurrentLogs ( ){\n+            if(self.selected().length === 1){\n+                var id = self.selected()[0].data.id;\n+                var promise = self.getLogs(id);\n+            }\n+        };\n+\n+        /* filesData is the link that loads tree data. This function refreshes that information. */\n+        self.updateFilesData = function _updateFilesData (linkObject) {\n+            if (linkObject.link !== self.currentLink) {\n+                self.updateBreadcrumbs(linkObject);\n+                self.updateList(linkObject);\n+                self.currentLink = linkObject.link;\n+            }\n+            self.showSidebar(false);\n+        };\n+\n+        // INFORMATION PANEL\n+        /* Defines the current selected item so appropriate information can be shown */\n+        self.selected = m.prop([]);\n+        self.updateSelected = function _updateSelected (selectedList){\n+            self.selected(selectedList);\n+            self.getCurrentLogs();\n+        };\n+\n+        // USER FILTER\n+        self.activeFilter = m.prop({});\n+        self.updateFilter = function _updateFilter (filter) {\n+            self.activeFilter(filter);\n+            self.updateFilesData(filter);\n+        };\n+\n+        self.removeProjectFromCollections = function _removeProjectFromCollection () {\n+            // Removes selected items from collect\n+            var collection = self.activeFilter().data.node;\n+            self.selected().map(function _removeProjectFromCollectionsMap (item){\n+                m.request({\n+                    method : 'DELETE',\n+                    url : collection.links.self + 'node_links/' + item.data.id + '/',\n+                    config : xhrconfig\n+                }).then(function _removeProjectFromCollectionsSuccess(result){\n+                    console.log(result);\n+                }, function _removeProjectFromCollectionsFail(result){\n+                    console.log(result);\n+                });\n+            });\n+        };\n+        // GETTING THE NODES\n+        self.updateList = function _updateList (linkObject, success, error){\n+            if(linkObject.data.systemCollection === 'nodes' && self.allProjectsLoaded()){\n+                self.data(self.allProjects());\n+                self.reload(true);\n+                self.refreshView(false);\n+                return;\n+            }\n+            self.refreshView(true);\n+            m.redraw();\n+            if (success === undefined){\n+                success = self.updateListSuccess;\n+            }\n+            if(linkObject.data.systemCollection === 'nodes'){\n+                self.loadingAllNodes = true;\n+            }\n+            if (error === undefined){\n+                error = self.updateListError;\n+            }\n+            var url = linkObject.link;\n+            if (typeof url !== 'string'){\n+                throw new Error('Url argument for updateList needs to be string');\n+            }\n+            var promise = m.request({method : 'GET', url : url, config : xhrconfig});\n+            promise.then(success, error);\n+            return promise;\n+        };\n+        self.updateListSuccess = function _updateListSuccess (value) {\n+            if(self.loadingNodePages){\n+                self.data().data = self.data().data.concat(value.data);\n+            } else {\n+                self.data(value);\n+            }\n+            if(!value.data[0]){ // If we have projects\n+                var lastcrumb = self.breadcrumbs()[self.breadcrumbs().length-1];\n+                if(lastcrumb.type === 'collection'){\n+                    if(lastcrumb.data.systemCollection === 'nodes'){\n+                        self.nonLoadTemplate(m('.fb-non-load-template.m-md.p-md.osf-box',\n+                            'You have not created any projects yet.'));\n+                    } else if (lastcrumb.data.systemCollection === 'registrations'){\n+                        self.nonLoadTemplate(m('.fb-non-load-template.m-md.p-md.osf-box',\n+                            'You have not made any registrations yet.'));\n+                    } else {\n+                        self.nonLoadTemplate(m('.fb-non-load-template.m-md.p-md.osf-box',\n+                            'This collection has no projects. To add projects go to \"All My Projects\" collection; drag and drop projects into the collection link'));\n+                    }\n+                } else {\n+                    self.nonLoadTemplate(m('.fb-non-load-template.m-md.p-md.osf-box.text-center', [\n+                        'This project has no subcomponents.',\n+                        m.component(AddProject, {\n+                            buttonTemplate : m('.btn.btn-link[data-toggle=\"modal\"][data-target=\"#addSubcomponent\"]', 'Add new Subcomponent'),\n+                            parentID : self.breadcrumbs()[self.breadcrumbs().length-1].data.id,\n+                            modalID : 'addSubcomponent',\n+                            stayCallback : function _stayCallback_inPanel() {\n+                                self.allProjectsLoaded(false);\n+                                self.updateList(self.breadcrumbs()[self.breadcrumbs().length-1]);\n+                            }\n+                        })\n+                    ]));\n+                }\n+            }\n+            // if we have more pages keep loading the pages\n+            if (value.links.next) {\n+                self.loadingNodePages = true;\n+                var collData = {};\n+                if(!self.allProjectsLoaded()) {\n+                    collData = { systemCollection : 'nodes' };\n+                }\n+                self.updateList({link : value.links.next, data : collData });\n+                return; // stop here so the reloads below don't run\n+            } else {\n+                self.loadingNodePages = false;\n+            }\n+            if(self.loadingAllNodes) {\n+                self.data().data = _makeTree(self.data().data);\n+                self.allProjects(self.data());\n+                self.generateFiltersList();\n+                self.loadingAllNodes = false;\n+                self.allProjectsLoaded(true);\n+            }\n+            self.reload(true);\n+            self.refreshView(false);\n+        };\n+        self.updateListError = function _updateListError (result){\n+            self.nonLoadTemplate(m('.fb-error.text-danger.m-t-lg', [\n+                m('p', m('i.fa.fa-exclamation-circle')),\n+                m('p','Projects for this selection couldn\\'t load.'),\n+                m('p', m('.btn.btn-default', {\n+                    onclick : self.updateFilter.bind(null, self.systemCollections[0])\n+                },' Reload \\'All My Projects\\''))\n+            ]));\n+            self.data().data = [];\n+            self.refreshView(false);\n+            throw new Error('Receiving initial data for File Browser failed. Please check your url');\n+        };\n+        self.generateFiltersList = function _generateFilterList () {\n+            self.users = {};\n+            self.tags = {};\n+            self.data().data.map(function _generateFiltersListMap(item){\n+                var contributors = item.embeds.contributors.data ? item.embeds.contributors.data : [];\n+                for(var i = 0; i < contributors.length; i++) {\n+                    var u = contributors[i];\n+                    if(self.users[u.id] === undefined) {\n+                        self.users[u.id] = {\n+                            data : u,\n+                            count: 1\n+                        };\n+                    } else {\n+                        self.users[u.id].count++;\n+                    }\n+                }\n+\n+                var tags = item.attributes.tags ? item.attributes.tags : [];\n+                for(var j = 0; j < tags.length; j++) {\n+                    var t = tags[j];\n+                    if(self.tags[t] === undefined) {\n+                        self.tags[t] = 1;\n+                    } else {\n+                        self.tags[t]++;\n+                    }\n+                }\n+            });\n+\n+            // Add to lists with numbers\n+            for (var user in self.users){\n+                var u2 = self.users[user];\n+                self.nameFilters.push(new LinkObject('name', { id : u2.data.id, count : u2.count, query : { 'related_counts' : true }}, u2.data.embeds.users.data.attributes.full_name));\n+            }\n+            for (var tag in self.tags){\n+                var t2 = self.tags[tag];\n+                self.tagFilters.push(new LinkObject('tag', { tag : tag, count : t2, query : { 'related_counts' : true }}, tag));\n+            }\n+        };\n+\n+        // BREADCRUMBS\n+        self.updateBreadcrumbs = function _updateBreadcrumbs (linkObject){\n+            if (linkObject.type === 'collection' || linkObject.type === 'name' || linkObject.type === 'tag'){\n+                self.breadcrumbs([linkObject]);\n+                return;\n+            }\n+            if (linkObject.placement === 'breadcrumb'){\n+                self.breadcrumbs().splice(linkObject.index+1, self.breadcrumbs().length-linkObject.index-1);\n+                return;\n+            }\n+            if(linkObject.ancestors.length > 0){\n+                linkObject.ancestors.forEach(function(item){\n+                    var ancestorLink = new LinkObject('node', item.data, item.data.name);\n+                    self.breadcrumbs().push(ancestorLink);\n+                });\n+            }\n+            self.breadcrumbs().push(linkObject);\n+        }.bind(self);\n+\n+        self.sidebarInit = function _sidebarInit (element, isInit) {\n+            $('[data-toggle=\"tooltip\"]').tooltip();\n+        };\n+\n+        self.init = function _init_fileBrowser() {\n+            self.updateList(self.systemCollections[0]);\n+        };\n+        self.init();\n+    },\n+    view : function (ctrl, args) {\n+        var mobile = window.innerWidth < 767; // true if mobile view\n+        var infoPanel = '';\n+        var poStyle = 'width : 72%'; // Other percentages are set in CSS in file-browser.css These are here because they change\n+        var sidebarButtonClass = 'btn-default';\n+        var projectCount = ctrl.data().data.length;\n+        if (ctrl.showInfo() && !mobile){\n+            infoPanel = m('.fb-infobar', m.component(Information, ctrl));\n+            poStyle = 'width : 47%';\n+        }\n+        if(ctrl.showSidebar()){\n+            sidebarButtonClass = 'btn-primary';\n+        }\n+        if (mobile) {\n+            poStyle = 'width : 100%';\n+        } else {\n+            ctrl.showSidebar(true);\n+        }\n+\n+        return [\n+            m('.fb-header.row', [\n+                m('.col-xs-12.col-sm-6', m.component(Breadcrumbs,ctrl)),\n+                m('.fb-buttonRow.col-xs-12.col-sm-6', [\n+                    mobile ? m('button.btn.btn-sm.m-r-sm', {\n+                        'class' : sidebarButtonClass,\n+                        onclick : function () {\n+                            ctrl.showSidebar(!ctrl.showSidebar());\n+                        }\n+                    }, m('.fa.fa-bars')) : '',\n+                    m('span.m-r-md.hidden-xs', projectCount === 1 ? projectCount + ' Project' : projectCount + ' Projects'),\n+                    m('#poFilter.m-r-xs')\n+                ])\n+            ]),\n+            ctrl.showSidebar() ?\n+            m('.fb-sidebar', { config : ctrl.sidebarInit}, [\n+                mobile ? [ m('.fb-dismiss', m('button.close[aria-label=\"Close\"]', {\n+                    onclick : function () {\n+                        ctrl.showSidebar(false);\n+                    }\n+                }, [\n+                    m('span[aria-hidden=\"true\"]','×')\n+                ])),\n+                    m('p.p-sm.text-center.text-muted', [\n+                        'Select a list below to see the projects. or click ',\n+                        m('i.fa.fa-bars'),\n+                        ' button above to toggle.'\n+                    ])\n+                ] : '',\n+                m.component(Collections, ctrl),\n+                m.component(Filters, {\n+                    activeFilter : ctrl.activeFilter,\n+                    updateFilter : ctrl.updateFilter,\n+                    nameFilters : ctrl.nameFilters,\n+                    tagFilters : ctrl.tagFilters\n+                })\n+            ]) : '',\n+            mobile && ctrl.showSidebar() ? '' : m('.fb-main', { style : poStyle },[\n+                ctrl.refreshView() ? m('.spinner-div', m('i.fa.fa-refresh.fa-spin')) : '',\n+                ctrl.data().data.length === 0 ? ctrl.nonLoadTemplate() : m('#poOrganizer',  m.component( ProjectOrganizer, {\n+                        filesData : ctrl.data,\n+                        updateSelected : ctrl.updateSelected,\n+                        updateFilesData : ctrl.updateFilesData,\n+                        LinkObject : LinkObject,\n+                        reload : ctrl.reload,\n+                        dragContainment : args.wrapperSelector,\n+                        allProjects : ctrl.allProjects\n+                    })\n+                )\n+            ]),\n+            mobile ? '' : m('.fb-info-toggle',{\n+                    onclick : function _showInfoOnclick(){\n+                        ctrl.showInfo(!ctrl.showInfo());\n+                    }\n+                },\n+                ctrl.showInfo() ? m('i.fa.fa-chevron-right') :  m('i.fa.fa-chevron-left')\n+            ),\n+            infoPanel,\n+            m.component(Modals, ctrl)\n+        ];\n+    }\n+};\n+\n+/**\n+ * Collections Module.\n+ * @constructor\n+ */\n+var Collections  = {\n+    controller : function(args){\n+        var self = this;\n+        self.newCollectionName = m.prop('');\n+        self.newCollectionRename = m.prop('');\n+        self.dismissModal = function () {\n+            $('.modal').modal('hide');\n+        };\n+        self.currentPage = m.prop(1);\n+        self.totalPages = m.prop(1);\n+        self.calculateTotalPages = function _calculateTotalPages(result){\n+            if(result){ // If this calculation comes after GET call to collections\n+                self.totalPages(Math.ceil((result.links.meta.total + args.systemCollections.length)/self.pageSize()));\n+            } else {\n+                self.totalPages(Math.ceil((self.collections().length)/self.pageSize()));\n+            }\n+        };\n+        self.pageSize = m.prop(5);\n+        self.validation = m.prop(false);\n+        self.validationError = m.prop('');\n+        self.showCollectionMenu = m.prop(false); // Show hide ellipsis menu for collections\n+        self.collectionMenuObject = m.prop({item : {label:null}, x : 0, y : 0}); // Collection object to complete actions on menu\n+        self.resetCollectionMenu = function () {\n+            self.collectionMenuObject({item : {label:null}, x : 0, y : 0});\n+        };\n+        self.updateCollectionMenu = function _updateCollectionMenu (item, event) {\n+            var offset = $(event.target).offset();\n+            var x = offset.left;\n+            var y = offset.top;\n+            if (event.view.innerWidth < 767){\n+                x = x-105; // width of this menu plus padding\n+                y = y-270; // fixed height from collections parent to top with adjustments for this menu div\n+            }\n+            self.showCollectionMenu(true);\n+            item.renamedLabel = item.label;\n+            self.collectionMenuObject({\n+                item : item,\n+                x : x,\n+                y : y\n+            });\n+        };\n+        // Default system collections\n+        self.collections = m.prop([\n+            new LinkObject('collection', { path : 'users/me/nodes/', query : { 'related_counts' : true, 'page[size]'  : 12, 'embed' : 'contributors' }, systemCollection : 'nodes'}, 'All My Projects'),\n+            new LinkObject('collection', { path : 'users/me/registrations/', query : { 'related_counts' : true, 'page[size]'  : 12, 'embed' : 'contributors'}, systemCollection : 'registrations'}, 'All My Registrations')\n+        ]);\n+        // Load collection list\n+        var loadCollections = function _loadCollections (url){\n+            var promise = m.request({method : 'GET', url : url, config : xhrconfig});\n+            promise.then(function(result){\n+                result.data.forEach(function(node){\n+                    var count = node.relationships.linked_nodes.links.related.meta.count;\n+                    self.collections().push(new LinkObject('collection', { path : 'collections/' + node.id + '/linked_nodes/', query : { 'related_counts' : true, 'embed' : 'contributors' }, systemCollection : false, node : node, count : m.prop(count) }, node.attributes.title));\n+                });\n+                if(result.links.next){\n+                    loadCollections(result.links.next);\n+                }\n+            });\n+            promise.then(self.calculateTotalPages());\n+        };\n+        self.init = function _collectionsInit (element, isInit) {\n+            var collectionsUrl = $osf.apiV2Url('collections/', { query : {'related_counts' : true, 'page[size]' : self.pageSize(), 'sort' : 'date_created', 'embed' : 'node_links'}});\n+            loadCollections(collectionsUrl);\n+            args.activeFilter(self.collections()[0]);\n+\n+            $(window).click(function(event){\n+                var target = $(event.target);\n+                if(!target.hasClass('collectionMenu') && !target.hasClass('fa-ellipsis-v') && target.parents('.collection').length === 0) {\n+                    self.showCollectionMenu(false);\n+                    m.redraw(); // we have to force redraw here\n+                }\n+            });\n+        };\n+\n+        self.addCollection = function _addCollection () {\n+            var url = $osf.apiV2Url('collections/', {});\n+            var data = {\n+                'data': {\n+                    'type': 'collections',\n+                    'attributes': {\n+                        'title': self.newCollectionName(),\n+                    }\n+                }\n+            };\n+            var promise = m.request({method : 'POST', url : url, config : xhrconfig, data : data});\n+            promise.then(function(result){\n+                var node = result.data;\n+                var count = node.relationships.linked_nodes.links.related.meta.count || 0;\n+                self.collections().push(new LinkObject('collection', { path : 'collections/' + node.id + '/linked_nodes/', query : { 'related_counts' : true }, systemCollection : false, node : node, count : m.prop(count) }, node.attributes.title));\n+                args.sidebarInit();\n+            });\n+            self.newCollectionName('');\n+            self.dismissModal();\n+            self.calculateTotalPages();\n+            return promise;\n+        };\n+        self.deleteCollection = function _deleteCollection(){\n+            var url = self.collectionMenuObject().item.data.node.links.self;\n+            var promise = m.request({method : 'DELETE', url : url, config : xhrconfig});\n+            promise.then(function(result){\n+                for ( var i = 0; i < self.collections().length; i++) {\n+                    var item = self.collections()[i];\n+                    if (item.data.node && item.data.node.id === self.collectionMenuObject().item.data.node.id){\n+                        self.collections().splice(i, 1);\n+                        break;\n+                    }\n+                }\n+            });\n+            self.dismissModal();\n+            self.calculateTotalPages();\n+            return promise;\n+        };\n+        self.renameCollection = function _renameCollection() {\n+            var url = self.collectionMenuObject().item.data.node.links.self;\n+            var nodeId = self.collectionMenuObject().item.data.node.id;\n+            var title = self.collectionMenuObject().item.renamedLabel;\n+            var data = {\n+                'data': {\n+                    'type': 'collections',\n+                    'id':  nodeId,\n+                    'attributes': {\n+                        'title': title\n+                    }\n+                }\n+            };\n+            var promise = m.request({method : 'PATCH', url : url, config : xhrconfig, data : data});\n+            promise.then(function(result){\n+                self.collectionMenuObject().item.label = title;\n+                m.redraw(true);\n+            });\n+            self.dismissModal();\n+            self.validation(false);\n+            return promise;\n+        };\n+        self.applyDroppable = function _applyDroppable ( ){\n+            $('.fb-collections ul>li').droppable({\n+                hoverClass: 'bg-color-hover',\n+                drop: function( event, ui ) {\n+                    var collection = self.collections()[$(this).attr('data-index')];\n+                    var dataArray = [];\n+                    // If multiple items are dragged they have to be selected to make it work\n+                    if (args.selected().length > 1) {\n+                        args.selected().map(function(item){\n+                            dataArray.push(buildCollectionNodeData(item.data.id));\n+                        });\n+                    } else {\n+                        // if single items are passed use the event information\n+                        dataArray.push(buildCollectionNodeData(ui.draggable.find('.title-text>a').attr('data-nodeID'))); // data-nodeID attribute needs to be set in project organizer building title column\n+                    }\n+                    function saveNodetoCollection (index) {\n+                        function doNext (){\n+                            if(dataArray[index+1]){\n+                                saveNodetoCollection(index+1);\n+                            }\n+                            collection.data.count(collection.data.count()+1);\n+                        }\n+                        m.request({\n+                            method : 'POST',\n+                            url : collection.data.node.links.self + 'node_links/', //collection.data.node.relationships.linked_nodes.links.related.href,\n+                            config : xhrconfig,\n+                            data : dataArray[index]\n+                        }).then(doNext, doNext); // In case of success or error. It doesn't look like mithril has a general .done method\n+                    }\n+                    if(dataArray.length > 0){\n+                        saveNodetoCollection(0);\n+                    }\n+                }\n+            });\n+        };\n+        self.validateName = function _validateName (val){\n+            if (val === 'Bookmarks') {\n+                self.validation(false);\n+                self.validationError('Your collection name can\\'t be \"Bookmarks\", because bookmarks feature requires this name. Please select any other name. ');","path":"website/static/js/fileBrowser.js","position":664,"original_position":664,"commit_id":"0dccb8fcf54fa53f3f13187cc5a7ecff23565b7d","original_commit_id":"0dccb8fcf54fa53f3f13187cc5a7ecff23565b7d","user":{"login":"sloria","id":2379650,"avatar_url":"https://avatars.githubusercontent.com/u/2379650?v=3","gravatar_id":"","url":"https://api.github.com/users/sloria","html_url":"https://github.com/sloria","followers_url":"https://api.github.com/users/sloria/followers","following_url":"https://api.github.com/users/sloria/following{/other_user}","gists_url":"https://api.github.com/users/sloria/gists{/gist_id}","starred_url":"https://api.github.com/users/sloria/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sloria/subscriptions","organizations_url":"https://api.github.com/users/sloria/orgs","repos_url":"https://api.github.com/users/sloria/repos","events_url":"https://api.github.com/users/sloria/events{/privacy}","received_events_url":"https://api.github.com/users/sloria/received_events","type":"User","site_admin":false},"body":"Possible revision:\r\n\r\n> \"Bookmarks\" is a reserved collection name. Please use another name.","created_at":"2016-01-29T19:08:01Z","updated_at":"2016-01-29T19:08:01Z","html_url":"https://github.com/CenterForOpenScience/osf.io/pull/4887#discussion_r51301353","pull_request_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/4887","_links":{"self":{"href":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/comments/51301353"},"html":{"href":"https://github.com/CenterForOpenScience/osf.io/pull/4887#discussion_r51301353"},"pull_request":{"href":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/4887"}}},"pull_request":{"url":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/4887","id":55907734,"html_url":"https://github.com/CenterForOpenScience/osf.io/pull/4887","diff_url":"https://github.com/CenterForOpenScience/osf.io/pull/4887.diff","patch_url":"https://github.com/CenterForOpenScience/osf.io/pull/4887.patch","issue_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/issues/4887","number":4887,"state":"open","locked":false,"title":"Project Organizer redesign","user":{"login":"caneruguz","id":1314003,"avatar_url":"https://avatars.githubusercontent.com/u/1314003?v=3","gravatar_id":"","url":"https://api.github.com/users/caneruguz","html_url":"https://github.com/caneruguz","followers_url":"https://api.github.com/users/caneruguz/followers","following_url":"https://api.github.com/users/caneruguz/following{/other_user}","gists_url":"https://api.github.com/users/caneruguz/gists{/gist_id}","starred_url":"https://api.github.com/users/caneruguz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caneruguz/subscriptions","organizations_url":"https://api.github.com/users/caneruguz/orgs","repos_url":"https://api.github.com/users/caneruguz/repos","events_url":"https://api.github.com/users/caneruguz/events{/privacy}","received_events_url":"https://api.github.com/users/caneruguz/received_events","type":"User","site_admin":false},"body":"## Purpose\r\nThis PR is changing the functionality of the project organizer on the dashboard page by changing the way project organizer functions work and creating a new paradigm for handling project related tasks including collections.\r\n\r\n## Changes\r\n- All project related aspects are in fileBrowser.js file \r\n- A new method of adding projects is used in addProjectPlugin.js\r\n- The way activity logs are presented are changed into a js plugin with logTextParser.js \r\n- API adjustments have been made for end points to provide the data the new dashboard needs \r\n\r\n## Side Effects\r\nThis is a first step PR to get the majority of the items through code review. Some elements are missing\r\n- Removing project from collection is waiting on another API PR\r\n- Prereg adding existing project feature is broken, will be fixed soon\r\n- Some logs do not have text parsing yet\r\n- Dashboard name will be changed to My Project in routes, url, mako and tests\r\n\r\nBecause this PR is large and the current PR contains 98% of the work it should be merged in to dashboard while other minor work continues. \r\n\r\nThis PR relates to these Jira tickets:\r\nhttps://openscience.atlassian.net/browse/OSF-4797 [OSF-4797]\r\nhttps://openscience.atlassian.net/browse/OSF-4799 [OSF-4799]\r\nhttps://openscience.atlassian.net/browse/OSF-4800 [OSF-4800]\r\nhttps://openscience.atlassian.net/browse/OSF-5155 [OSF-5155]\r\nhttps://openscience.atlassian.net/browse/OSF-5262 [OSF-5262]\r\nhttps://openscience.atlassian.net/browse/OSF-5263 [OSF-5263]\r\nhttps://openscience.atlassian.net/browse/OSF-5408 [OSF-5408]\r\n\r\n### Screenshot:\r\n![screen shot 2016-01-13 at 9 59 19 am](https://cloud.githubusercontent.com/assets/1314003/12297710/5fb03dbc-b9dc-11e5-9c79-0647d230e4f6.png)\r\n","created_at":"2016-01-13T14:59:58Z","updated_at":"2016-01-29T19:08:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ebfc175ba56be3745f988d5ca697e59b0a27eda1","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/4887/commits","review_comments_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/4887/comments","review_comment_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/issues/4887/comments","statuses_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/statuses/0dccb8fcf54fa53f3f13187cc5a7ecff23565b7d","head":{"label":"caneruguz:dashboard-redesign-component-3","ref":"dashboard-redesign-component-3","sha":"0dccb8fcf54fa53f3f13187cc5a7ecff23565b7d","user":{"login":"caneruguz","id":1314003,"avatar_url":"https://avatars.githubusercontent.com/u/1314003?v=3","gravatar_id":"","url":"https://api.github.com/users/caneruguz","html_url":"https://github.com/caneruguz","followers_url":"https://api.github.com/users/caneruguz/followers","following_url":"https://api.github.com/users/caneruguz/following{/other_user}","gists_url":"https://api.github.com/users/caneruguz/gists{/gist_id}","starred_url":"https://api.github.com/users/caneruguz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caneruguz/subscriptions","organizations_url":"https://api.github.com/users/caneruguz/orgs","repos_url":"https://api.github.com/users/caneruguz/repos","events_url":"https://api.github.com/users/caneruguz/events{/privacy}","received_events_url":"https://api.github.com/users/caneruguz/received_events","type":"User","site_admin":false},"repo":{"id":27096151,"name":"osf.io","full_name":"caneruguz/osf.io","owner":{"login":"caneruguz","id":1314003,"avatar_url":"https://avatars.githubusercontent.com/u/1314003?v=3","gravatar_id":"","url":"https://api.github.com/users/caneruguz","html_url":"https://github.com/caneruguz","followers_url":"https://api.github.com/users/caneruguz/followers","following_url":"https://api.github.com/users/caneruguz/following{/other_user}","gists_url":"https://api.github.com/users/caneruguz/gists{/gist_id}","starred_url":"https://api.github.com/users/caneruguz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caneruguz/subscriptions","organizations_url":"https://api.github.com/users/caneruguz/orgs","repos_url":"https://api.github.com/users/caneruguz/repos","events_url":"https://api.github.com/users/caneruguz/events{/privacy}","received_events_url":"https://api.github.com/users/caneruguz/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/caneruguz/osf.io","description":"Facilitating Open Science","fork":true,"url":"https://api.github.com/repos/caneruguz/osf.io","forks_url":"https://api.github.com/repos/caneruguz/osf.io/forks","keys_url":"https://api.github.com/repos/caneruguz/osf.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/caneruguz/osf.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/caneruguz/osf.io/teams","hooks_url":"https://api.github.com/repos/caneruguz/osf.io/hooks","issue_events_url":"https://api.github.com/repos/caneruguz/osf.io/issues/events{/number}","events_url":"https://api.github.com/repos/caneruguz/osf.io/events","assignees_url":"https://api.github.com/repos/caneruguz/osf.io/assignees{/user}","branches_url":"https://api.github.com/repos/caneruguz/osf.io/branches{/branch}","tags_url":"https://api.github.com/repos/caneruguz/osf.io/tags","blobs_url":"https://api.github.com/repos/caneruguz/osf.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/caneruguz/osf.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/caneruguz/osf.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/caneruguz/osf.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/caneruguz/osf.io/statuses/{sha}","languages_url":"https://api.github.com/repos/caneruguz/osf.io/languages","stargazers_url":"https://api.github.com/repos/caneruguz/osf.io/stargazers","contributors_url":"https://api.github.com/repos/caneruguz/osf.io/contributors","subscribers_url":"https://api.github.com/repos/caneruguz/osf.io/subscribers","subscription_url":"https://api.github.com/repos/caneruguz/osf.io/subscription","commits_url":"https://api.github.com/repos/caneruguz/osf.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/caneruguz/osf.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/caneruguz/osf.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/caneruguz/osf.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/caneruguz/osf.io/contents/{+path}","compare_url":"https://api.github.com/repos/caneruguz/osf.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/caneruguz/osf.io/merges","archive_url":"https://api.github.com/repos/caneruguz/osf.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/caneruguz/osf.io/downloads","issues_url":"https://api.github.com/repos/caneruguz/osf.io/issues{/number}","pulls_url":"https://api.github.com/repos/caneruguz/osf.io/pulls{/number}","milestones_url":"https://api.github.com/repos/caneruguz/osf.io/milestones{/number}","notifications_url":"https://api.github.com/repos/caneruguz/osf.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/caneruguz/osf.io/labels{/name}","releases_url":"https://api.github.com/repos/caneruguz/osf.io/releases{/id}","deployments_url":"https://api.github.com/repos/caneruguz/osf.io/deployments","created_at":"2014-11-24T21:33:09Z","updated_at":"2015-06-14T17:00:49Z","pushed_at":"2016-01-27T18:29:27Z","git_url":"git://github.com/caneruguz/osf.io.git","ssh_url":"git@github.com:caneruguz/osf.io.git","clone_url":"https://github.com/caneruguz/osf.io.git","svn_url":"https://github.com/caneruguz/osf.io","homepage":"http://osf.io","size":119346,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"develop"}},"base":{"label":"CenterForOpenScience:feature/dashboard","ref":"feature/dashboard","sha":"5aabd44b8d63b20599536a7f232ea74dff040702","user":{"login":"CenterForOpenScience","id":3344584,"avatar_url":"https://avatars.githubusercontent.com/u/3344584?v=3","gravatar_id":"","url":"https://api.github.com/users/CenterForOpenScience","html_url":"https://github.com/CenterForOpenScience","followers_url":"https://api.github.com/users/CenterForOpenScience/followers","following_url":"https://api.github.com/users/CenterForOpenScience/following{/other_user}","gists_url":"https://api.github.com/users/CenterForOpenScience/gists{/gist_id}","starred_url":"https://api.github.com/users/CenterForOpenScience/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/CenterForOpenScience/subscriptions","organizations_url":"https://api.github.com/users/CenterForOpenScience/orgs","repos_url":"https://api.github.com/users/CenterForOpenScience/repos","events_url":"https://api.github.com/users/CenterForOpenScience/events{/privacy}","received_events_url":"https://api.github.com/users/CenterForOpenScience/received_events","type":"Organization","site_admin":false},"repo":{"id":10199599,"name":"osf.io","full_name":"CenterForOpenScience/osf.io","owner":{"login":"CenterForOpenScience","id":3344584,"avatar_url":"https://avatars.githubusercontent.com/u/3344584?v=3","gravatar_id":"","url":"https://api.github.com/users/CenterForOpenScience","html_url":"https://github.com/CenterForOpenScience","followers_url":"https://api.github.com/users/CenterForOpenScience/followers","following_url":"https://api.github.com/users/CenterForOpenScience/following{/other_user}","gists_url":"https://api.github.com/users/CenterForOpenScience/gists{/gist_id}","starred_url":"https://api.github.com/users/CenterForOpenScience/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/CenterForOpenScience/subscriptions","organizations_url":"https://api.github.com/users/CenterForOpenScience/orgs","repos_url":"https://api.github.com/users/CenterForOpenScience/repos","events_url":"https://api.github.com/users/CenterForOpenScience/events{/privacy}","received_events_url":"https://api.github.com/users/CenterForOpenScience/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/CenterForOpenScience/osf.io","description":"Facilitating Open Science","fork":false,"url":"https://api.github.com/repos/CenterForOpenScience/osf.io","forks_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/forks","keys_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/teams","hooks_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/hooks","issue_events_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/issues/events{/number}","events_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/events","assignees_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/assignees{/user}","branches_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/branches{/branch}","tags_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/tags","blobs_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/statuses/{sha}","languages_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/languages","stargazers_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/stargazers","contributors_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/contributors","subscribers_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/subscribers","subscription_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/subscription","commits_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/contents/{+path}","compare_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/merges","archive_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/downloads","issues_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/issues{/number}","pulls_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls{/number}","milestones_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/milestones{/number}","notifications_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/labels{/name}","releases_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/releases{/id}","deployments_url":"https://api.github.com/repos/CenterForOpenScience/osf.io/deployments","created_at":"2013-05-21T15:53:37Z","updated_at":"2016-01-29T14:15:34Z","pushed_at":"2016-01-29T19:05:03Z","git_url":"git://github.com/CenterForOpenScience/osf.io.git","ssh_url":"git@github.com:CenterForOpenScience/osf.io.git","clone_url":"https://github.com/CenterForOpenScience/osf.io.git","svn_url":"https://github.com/CenterForOpenScience/osf.io","homepage":"http://osf.io","size":264719,"stargazers_count":183,"watchers_count":183,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":143,"mirror_url":null,"open_issues_count":315,"forks":143,"open_issues":315,"watchers":183,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/4887"},"html":{"href":"https://github.com/CenterForOpenScience/osf.io/pull/4887"},"issue":{"href":"https://api.github.com/repos/CenterForOpenScience/osf.io/issues/4887"},"comments":{"href":"https://api.github.com/repos/CenterForOpenScience/osf.io/issues/4887/comments"},"review_comments":{"href":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/4887/comments"},"review_comment":{"href":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/CenterForOpenScience/osf.io/pulls/4887/commits"},"statuses":{"href":"https://api.github.com/repos/CenterForOpenScience/osf.io/statuses/0dccb8fcf54fa53f3f13187cc5a7ecff23565b7d"}}}}