Modified: comdev/projects.apache.org/trunk/site/js/projects.js URL: http://svn.apache.org/viewvc/comdev/projects.apache.org/trunk/site/js/projects.js?rev=1926394&r1=1926393&r2=1926394&view=diff ============================================================================== --- comdev/projects.apache.org/trunk/site/js/projects.js (original) +++ comdev/projects.apache.org/trunk/site/js/projects.js Fri Jun 13 16:27:11 2025 @@ -1,1613 +1,1959 @@ -/* - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - -*/ - -// ----- Global hashes used throughout the script ------ \\ - -var people = {}; // committer -> name lookups -var unixgroups = {}; // unix (ldap) groups (project -> committers lookup) -var committees = {}; // id -> committee info (chair, established, group, homepage, id, name, reporting, shortdesc) (current committees) -var committeesByName = {}; // name -> committee info -var retiredCommittees = {}; // retired committees information: id -> committee info (established, retired, homepage, id, name) -var projects = {}; // Projects -var podlings = {}; // current podlings -var podlingsHistory = {}; // Podlings history (now graduated or retired) -var repositories = {}; // source repositories id -> url - -// --------- Global helpers ----------- \\ - -function includeJs(jsFilePath) { - var js = document.createElement("script"); - - js.type = "text/javascript"; - js.src = jsFilePath; - - document.head.appendChild(js); -} - -includeJs("js/underscore-min.js"); - -function GetAsyncJSON(theUrl, xstate, callback) { - var xmlHttp = null; - if (window.XMLHttpRequest) { - xmlHttp = new XMLHttpRequest(); - } else { - xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); - } - xmlHttp.open("GET", theUrl, true); - xmlHttp.send(null); - xmlHttp.onreadystatechange = function(state) { - if (xmlHttp.readyState == 4 && xmlHttp.status == 200 || xmlHttp.status == 404) { - if (callback) { - if (xmlHttp.status == 404) { - callback({}, xstate); - } else { - callback(JSON.parse(xmlHttp.responseText), xstate); - } - } - } - } -} - -var urlErrors = [] -var fetchCount = 0; -// Fetch an array of URLs, each with their description and own callback plus a final callback -// Used to fetch everything before rendering a page that relies on multiple JSON sources. -function GetAsyncJSONArray(urls, finalCallback) { - var obj = document.getElementById('progress'); - if (fetchCount == 0 ) { - fetchCount = urls.length; - } - - if (urls.length > 0) { - var a = urls.shift(); - var URL = a[0]; - var desc = a[1]; - var cb = a[2]; - var xmlHttp = null; - if (window.XMLHttpRequest) { - xmlHttp = new XMLHttpRequest(); - } else { - xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); - } - - if (obj) { obj.innerHTML = "loading file #" + ( fetchCount - urls.length ) + " / " + fetchCount + "<br>" + desc } - - xmlHttp.open("GET", URL, true); - xmlHttp.onreadystatechange = function(state) { - if (xmlHttp.readyState == 4) { - if (cb) { - if (xmlHttp.status == 200) { - cb(JSON.parse(xmlHttp.responseText)); - } else { - urlErrors.push(URL) - cb({}); - } - } - GetAsyncJSONArray(urls, finalCallback); - } - } - xmlHttp.send(null); - } - else { - if (obj) { obj.innerHTML = "building page content..." } - finalCallback(); - } -} - -// See project_editor.js (not currently used) - -// ------------ Project information page ------------\\ - -function linkCommitterIndex(cid) { - var fullname = people[cid]; - var cl = isMember(cid) ? "member" : "committer"; - return "<a class='" + cl + "' title='" + cid + "' href='https://home.apache.org/phonebook.html?uid=" + cid + "' target='_blank'>" + fullname + "</a>"; -} - -function appendElementWithInnerHTML(obj,type,html) { - var child = document.createElement(type); - child.innerHTML = html; - obj.appendChild(child); - return child; -} - -function appendLiInnerHTML(ul,html) { - return appendElementWithInnerHTML(ul,'li',html); -} - -function projectIdToUnixGroup(projectId, pmcName) { - // Rerig the unix name and committee id - var unixgroup = projectId.split("-")[0]; - /* - Temp hack for podling names. TODO need to sort out generated names - */ - if (projectId.indexOf("incubator-") === 0) { - unixgroup = projectId.split("-")[1] - } - // special cases - if (unixgroup === "empire") unixgroup = "empire-db"; - if (unixgroup === "community") unixgroup = "comdev"; - if (pmcName === "attic") { - unixgroup = "attic"; - } - return unixgroup; -} - -function renderProjectPage(project, projectId) { - var obj = document.getElementById('contents'); - - if ((!project || !project.name) && projects[projectId]) { - // no DOAP file but known project: podling (loaded from podlings.json) - project = projects[projectId]; - } - if (!project || !project.name) { - obj.innerHTML = "<h2>Sorry, I don't have any information available about this project</h2>"; - return; - } - - fixProjectName(project); - var isIncubating = project && (project.podling || (project.pmc == 'incubator')); - - var unixgroup = projectIdToUnixGroup(projectId, project && project.pmc); - - var committeeId = isIncubating ? 'incubator' : unixgroup; - if (!committees[unixgroup]) { - // at least one committee has a unix group that is different from committee id: webservices (group=ws), see parsecommittees.py#group_ids - // search instead of hard-coding the currently known case - for (p in committees) { - if (committees[p].group == unixgroup) { - committeeId = p; - break; - } - } - } - var committee = committees[committeeId]; - if (!committee) { - obj.innerHTML = "<h2>Cannot find the PMC '" + committeeId + "' for this project. Check the DOAP is correct.</h2>"; - return; - } - - // Start by splitting the name, thus fetching the root name of the project, and not the sub-project. - var description = ""; - if (project) { - if (!_.isEmpty(project.description)) { - description = project.description; - } else if (!_.isEmpty(project.shortdesc)) { - description = project.shortdesc; - } else if (!_.isEmpty(committee.shortdesc)) { - description = committee.shortdesc; - } else { - description = "No description available"; - } - } - - // Title + description - obj.innerHTML = "<h1>" + project.name + " <font size='-1'>(a project managed by the <a href='committee.html?" + committeeId + "'>" + committee.name + " Committee</a>)</font></h1>"; - - // project description - appendElementWithInnerHTML(obj,'p',description.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a) { return "<p>"+a+"</p>"})); - - var ul = document.createElement('ul'); - - // Base data - appendElementWithInnerHTML(obj,'h4',"Project base data:"); - - if (project.description && project.shortdesc) { - appendLiInnerHTML(ul, "<b>Short description:</b> " + project.shortdesc); - } - - // Categories - if (project.category) { - var arr = project.category.split(/,\s*/); - var pls = ""; - for (i in arr) { - var cat = arr[i]; - // categories are downcased so fix up the anchor - pls += "<a href='projects.html?category#" + cat.toLowerCase() + "'>" + cat + "</a> "; - } - appendLiInnerHTML(ul, "<b>Category:</b> " + pls); - } - - // Website - if (project.homepage) { - appendLiInnerHTML(ul, "<b>Website:</b> <a href='" + project.homepage + "' target='_blank'>" + project.homepage + "</a>"); - } - if (isIncubating) { - appendLiInnerHTML(ul, "<b>Project status:</b> <span class='ppodling'>Incubating</span>"); - } else if (committeeId != 'attic') { - appendLiInnerHTML(ul, "<b>Project status:</b> <span class='pactive'>Active</span>"); - } else { - appendLiInnerHTML(ul, "<b>Project status:</b> <span class='pretired'>Retired</span>"); - } - - // Committers - if (isIncubating && unixgroups[unixgroup]) { - var commitl = []; - var commitgroup = unixgroups[unixgroup]; - for (i in commitgroup) { - commitl.push(linkCommitterIndex(commitgroup[i])); - } - appendLiInnerHTML(ul, "<b>Committers (" + commitgroup.length + "):</b> <blockquote>" + commitl.join(", ") + "</blockquote>"); - } - - if (project.implements) { - var stds = document.createElement('ul'); - var impl; - for (impl in project.implements) { - impl = project.implements[impl]; - var std = ""; - if (impl.body) { - std += impl.body + ' '; - } - if (impl.id) { - std += "<a href='" + impl.url + "'>" + impl.id + "</a>: " + impl.title; - } else { - std += "<a href='" + impl.url + "'>" + impl.title + "</a>"; - } - appendLiInnerHTML(stds, std); - } - appendLiInnerHTML(ul, "<b>Implemented standards</b>").appendChild(stds); - } - - // doap/rdf - if (project.doap) { - appendLiInnerHTML(ul, "<b>Project data file:</b> <a href='" + project.doap + "' target='_blank'>DOAP RDF Source</a> (<a href='json/projects/" + projectId + ".json'>generated json</a>)"); - } else { - appendLiInnerHTML(ul, "<b>Project data file:</b> no <a href='https://projects.apache.org/create.html'>DOAP file</a> available"); - } - // maintainer - if (project.maintainer) { - var mt; - var maintainers = ""; - for (mt in project.maintainer) { - mt = project.maintainer[mt]; - if (mt.mbox) { - var id = mt.mbox; - id = id.substr(id.indexOf(':') + 1); - id = id.substr(0, id.indexOf('@')); - if (people[id]) { - maintainers += linkCommitterIndex(id) + " "; - } else { - maintainers += "<a href='" + mt.mbox + "'>" + mt.name + "</a> "; - } - } else { - maintainers += mt.name + " "; - } - } - appendLiInnerHTML(ul, "<b>Project data maintainer(s):</b> " + maintainers); - } - - obj.appendChild(ul); - - // Code data - appendElementWithInnerHTML(obj,'h4',"Development:"); - ul = document.createElement('ul'); - - if (project['programming-language']) { - var pl = project['programming-language']; - var arr = pl.split(/,\s*/); - var pls = ""; - for (i in arr) { - pls += "<a href='projects.html?language#" + arr[i] + "'>" + arr[i] + "</a> "; - } - appendLiInnerHTML(ul, "<b>Programming language:</b> " + pls); - } - - if (project['bug-database']) { - var bd = project['bug-database']; - var arr = bd.split(/,\s*/); - var bds = ""; - for (i in arr) { - bds += "<a href='" + arr[i] + "'>" + arr[i] + "</a> "; - } - appendLiInnerHTML(ul, "<b>Bug-tracking:</b> " + bds); - } - - if (project['mailing-list']) { - var ml = project['mailing-list']; - var xml = ml; - // email instead of link? - if (ml.match(/@/)) { - xml = "mailto:" + ml; - } - appendLiInnerHTML(ul, "<b>Mailing list(s):</b> <a href='" + xml + "'>" + ml + "</a>"); - } - - // repositories - if (project.repository) { - var r; - for (r in project.repository) { - r = project.repository[r]; - if (r.indexOf("svn") > 0) { - appendLiInnerHTML(ul, "<b>Subversion repository:</b> <a target=*_blank' href='" + r + "'>" + r + "</a>"); - } else if (r.indexOf("git") > 0) { - appendLiInnerHTML(ul, "<b>Git repository:</b> <a target=*_blank' href='" + r + "'>" + r + "</a>"); - } else { - appendLiInnerHTML(ul, "<b>Repository:</b> <a target=*_blank' href='" + r + "'>" + r + "</a>"); - } - } - } - - obj.appendChild(ul); - - // releases - appendElementWithInnerHTML(obj,'h4',"Releases <font size='-2'>(from DOAP)</font>:"); - ul = document.createElement('ul'); - if (project['download-page']) { - appendLiInnerHTML(ul, "<b>Download:</b> <a href='" + project['download-page'] + "' target='_blank'>" + project['download-page'] + "</a>"); - } - if (project.release) { - project.release.sort(function(a,b){// reverse date order (most recent first) - var ac = a.created ? a.created : '1970-01-01'; - var bc = b.created ? b.created : '1970-01-01'; - if(ac < bc) return 1; - if(ac > bc) return -1; - return 0;}); - var r; - for (r in project.release) { - r = project.release[r]; - var html = "<b>" + (r.revision ? r.revision : r.version) + "</b>"; - html += " (" + (r.created ? r.created : 'unknown') + ")"; - appendLiInnerHTML(ul, html + ": " + r.name); - } - } - obj.appendChild(ul); -} - - -function buildProjectPage() { - var projectId = document.location.search.substr(1); - GetAsyncJSON("json/projects/" + projectId + ".json?" + Math.random(), projectId, renderProjectPage) -} - -// extract committee name from repo name -function repoToCommittee(reponame) { - if (reponame.startsWith('empire-db')) { - return 'empire-db'; - } - return reponame.split('-')[0]; -} - -function renderCommitteePage(committeeId) { - var obj = document.getElementById('contents'); - - if (!committees[committeeId]) { - obj.innerHTML = "<h2>Sorry, I don't have any information available about this committee</h2>"; - return; - } - - var unixgroup = committeeId; // there are probably a few exceptions... - var committee = committees[committeeId]; - - obj.innerHTML = "<h1>" + committee.name + " Committee <font size='-2'>(also called PMC or <a href='https://www.apache.org/#projects-list' target='_blank'>Top Level Project</a>)</font></h1>"; - - var description; - if (!_.isEmpty(committee.shortdesc)) { - description = committee.shortdesc; - } else { - description = "Missing from https://whimsy.apache.org/public/ committee-info.json"; - } - - appendElementWithInnerHTML(obj, 'h4', "Description <font size='-2'>(from <a href='https://whimsy.apache.org/public/' target='_blank'>committee-info</a>)</a>:"); - - appendElementWithInnerHTML(obj,'p',description.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a) { return "<p>"+a+"</p>"})); - - appendElementWithInnerHTML(obj, 'h4', "Charter <font size='-2'>(from PMC data file)</a>:"); - - var charter; - if (!_.isEmpty(committee.charter)) { - charter = committee.charter; - } else { - charter = "Missing"; - } - - appendElementWithInnerHTML(obj,'p',charter.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a) { return "<p>"+a+"</p>"})); - - var ul = document.createElement('ul'); - - appendElementWithInnerHTML(obj, 'h4', "Committee data:"); - - appendLiInnerHTML(ul, "<b>Website:</b> <a href='" + committee.homepage + "' target='_blank'>" + committee.homepage + "</a>"); - - appendLiInnerHTML(ul, "<b>Committee established:</b> " + committee.established); - - // VP - appendLiInnerHTML(ul, "<b>PMC Chair:</b> " + linkCommitterIndex(committee.chair)); - - // Reporting cycle - var cycles = ["every month", "January, April, July, October", "February, May, August, November", "March, June, September, December"]; - var minutes = committee.name.substr("Apache ".length).replace(/ /g, '_'); - // does not work for APR and Logging Services currently - if (committeeId == 'apr') { - minutes = 'Apr'; - } else if (committeeId == 'logging') { - minutes = 'Logging'; - } - appendLiInnerHTML(ul, "<b>Reporting cycle:</b> " + cycles[committee.reporting] + ", see <a href='https://whimsy.apache.org/board/minutes/" + minutes + ".html' target='_blank'>minutes</a>"); - - // PMC - if (committee.roster) { // check we have the data - var pmcl = []; - for (i in committee.roster) { - pmcl.push(linkCommitterIndex(i)); - } - if (pmcl.length > 0) { - appendLiInnerHTML(ul, "<b>PMC Roster <font size='-1'>(from <a href='https://whimsy.apache.org/public/' target='_blank'>committee-info</a>; updated daily)</font> (" + pmcl.length + "):</b> <blockquote>" + pmcl.join(", ") + "</blockquote>"); - } else { - appendLiInnerHTML(ul, "<b>PMC Roster not found in committee-info.txt (check that Section 3 has been updated)</b>"); - } - } - - // Committers - if (unixgroups[unixgroup]) { - var commitl = []; - var commitgroup = unixgroups[unixgroup]; - for (i in commitgroup) { - commitl.push(linkCommitterIndex(commitgroup[i])); - } - appendLiInnerHTML(ul, "<b>Committers; updated daily (" + commitgroup.length + "):</b> <blockquote>" + commitl.join(", ") + "</blockquote>"); - } - - // rdf - if (committee.rdf) { - appendLiInnerHTML(ul, "<b>PMC data file:</b> <a href='" + committee.rdf + "' target='_blank'>RDF Source</a>"); - } - - obj.appendChild(ul); - - var subprojects = []; - for (p in projects) { - if (projects[p].pmc == committeeId) { - subprojects.push(p); - } - } - if (subprojects.length == 0) { - if (committeeId != 'labs') { - // if a committee did not declare any project (DOAP), consider there is a default one with the id of the committee - // only Labs doesn't manage projects - subprojects.push({ 'id': committeeId, 'name': committee.name, 'pmc': committeeId }); - } - } else { - appendElementWithInnerHTML(obj, 'h4', "Projects managed by this Committee:"); - - ul = document.createElement('ul'); - for (var p in subprojects.sort()) { - p = subprojects[p]; - appendLiInnerHTML(ul, projectLink(p)); - } - obj.appendChild(ul); - } - - var repos = []; - for (var r in repositories) { - if (committeeId == repoToCommittee(r)) { - repos.push(r); - } - } - if (repos.length > 0) { - appendElementWithInnerHTML(obj, 'h4', - "Source repositories managed by this Committee <font size='-2'>" + - "(from <a href='https://gitbox.apache.org/repositories.json'>ASF Git repos</a>" + - " and <a href='https://svn.apache.org/repos/asf/'>ASF SVN repos</a>)</font>:"); - - ul = document.createElement('ul'); - for (var r in repos.sort()) { - r = repos[r]; - var url = repositories[r]; - appendLiInnerHTML(ul, r + ": <a href='" + url + "'>" + url + "</a>"); - } - obj.appendChild(ul); - } -} - -function buildCommitteePage() { - var committeeId = document.location.search.substr(1); - renderCommitteePage(committeeId); -} - - -// ------------ Projects listing ------------\\ - -function camelCase(str) { - return str.replace(/^([a-z])(.+)$/, function(c,a,b) { return a.toUpperCase() + b.toLowerCase() } ); -} - -function committeeIcon() { - return "<img src='images/committee.png' title='Committee' style='vertical-align: middle; padding: 2px;'/> "; -} - -function projectIcon() { - return "<img src='images/project.png' title='Project' style='vertical-align: middle; padding: 2px;'/> " -} - -function committeeLink(id) { - var committee = committees[id]; - return "<a href='committee.html?" + id + "'>" + committee.name + "</a> - " + committee.shortdesc; -} - -function projectLink(id) { - var project = projects[id]; - if (!project) { - // not project id: perhaps committee id - project = committees[id]; - } - return "<a href='project.html?" + id + "'>" + project.name + "</a>"; -} - -function isMember(id) { - return _(unixgroups['member']).indexOf(id) >= 0; -} - -function sortProjects() { - var projectsSortedX = []; - var projectsSorted = []; - for (i in projects) { - projectsSortedX.push([i, projects[i].name.toLowerCase()]); - } - // compare names (already lower-cased) - projectsSortedX.sort(function(a,b) { return a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0 }) - for (i in projectsSortedX) { - projectsSorted.push(projectsSortedX[i][0]); - } - return projectsSorted; -} - -function renderProjectsByName() { - var obj = document.getElementById('list'); - obj.innerHTML = ""; - var projectsSorted = sortProjects(); - - // Project list - var ul = document.createElement('ul'); - for (var i in projectsSorted) { - var project = projectsSorted[i]; - appendLiInnerHTML(ul, projectIcon(projects[project].name) + projectLink(project)); - } - obj.appendChild(ul); -} - -function renderProjectsByLanguage() { - var obj = document.getElementById('list'); - obj.innerHTML = ""; - var projectsSorted = sortProjects(); - - // Compile Language array - var lingos = []; - var lcount = {}; - var i; - var x; - for (i in projects) { - if (projects[i]['programming-language']) { - var a = projects[i]['programming-language'].split(/,\s*/); - for (x in a) { - a[x] = camelCase(a[x]); - if (lingos.indexOf(a[x]) < 0) { - lingos.push(a[x]); - lcount[a[x]] = 0; - } - lcount[a[x]]++; - } - } - } - - // Construct language list - lingos.sort(); - var toc = document.createElement('p'); - var toch = document.createElement('h3'); - toch.textContent = 'TOC'; - toc.appendChild(toch); - var ul = document.createElement('ul'); - - var l; - for (l in lingos) { - var lang = lingos[l]; - var tocitem = document.createElement('a'); - tocitem.href="#" + lang; - tocitem.innerHTML=lang; - if (l > 0) { // divider - toc.appendChild(document.createTextNode(', ')); - } - toc.appendChild(tocitem); - var li = document.createElement('li'); - li.innerHTML = "<h3><a id='" + lang + "'>" + lang + " (" + lcount[lang] + ")</a>"+linkToHere(lang)+":</h3>"; - var cul = document.createElement('ul'); - for (i in projectsSorted) { - i = projectsSorted[i]; - if (projects[i]['programming-language']) { - var a = projects[i]['programming-language'].split(/,\s*/); - for (x in a) { - // Use same capitalisation as the language list - if (camelCase(a[x]) == lang) { - appendLiInnerHTML(cul, projectIcon(projects[i].name) + projectLink(i)); - } - } - } - } - li.appendChild(cul); - ul.appendChild(li); - } - - obj.appendChild(toc); - obj.appendChild(ul); - - if (location.hash.length > 1) { - setTimeout(function() { location.href = location.href;}, 250); - } -} - -function renderProjectsByCategory() { - var obj = document.getElementById('list'); - obj.innerHTML = ""; - var projectsSorted = sortProjects(); - - var cats = []; - var ccount = {}; - var i; - for (i in projects) { - if (projects[i].category) { - var a = projects[i].category.split(/,\s*/); - var x; - for (x in a) { - x = a[x].toLowerCase(); // must agree with downcase below - if (cats.indexOf(x) < 0) { - cats.push(x); - ccount[x] = 0; - } - ccount[x]++; - } - } - } - cats.sort(); - - // Construct category list - var toc = document.createElement('p'); - var toch = document.createElement('h3'); - toch.textContent = 'TOC'; - toc.appendChild(toch); - var ul = document.createElement('ul'); - - var l; - for (l in cats) { - var cat = cats[l]; - var tocitem = document.createElement('a'); - tocitem.href="#" + cat; - tocitem.innerHTML=cat; - if (l > 0) { // divider - toc.appendChild(document.createTextNode(', ')); - } - toc.appendChild(tocitem); - var li = document.createElement('li'); - li.innerHTML = "<h3><a id='" + cat + "'>" + cat + " (" + ccount[cat] + ")</a>"+linkToHere(cat)+":</h3>"; - var cul = document.createElement('ul'); - for (i in projectsSorted) { - i = projectsSorted[i]; - var project = projects[i]; - if (project.category) { - var a = project.category.split(/,\s*/); - for (x in a) { - x = a[x].toLowerCase(); // must agree with downcase above - if (x == cat) { - appendLiInnerHTML(cul, projectIcon(project.name) + projectLink(i)); - } - } - } - } - li.appendChild(cul); - ul.appendChild(li); - } - - obj.appendChild(toc); - obj.appendChild(ul); - - if (location.hash.length > 1) { - setTimeout(function() { location.href = location.href;}, 250); - } -} - -function renderProjectsByNumber() { - var obj = document.getElementById('list'); - obj.innerHTML = ""; - var projectsSorted = sortProjects(); - - var lens = []; - var lcount = {}; - for (projectId in projects) { - let unixGroup = projectIdToUnixGroup(projectId); - if (unixgroups[unixGroup] && projectId !== 'incubator') { - let len = unixgroups[unixGroup].length; - if (lens.indexOf(len) < 0) { - lens.push(len); - lcount[len] = 0; - } - lcount[len]++; - } - } - lens.sort(function(a,b) { return b - a }); - - // Construct date list - var ul = document.createElement('ul'); - - for (l in lens) { - var len = lens[l]; - var projectId; - for (projectId in projectsSorted) { - projectId = projectsSorted[projectId]; - let unixGroup = projectIdToUnixGroup(projectId); - if (unixgroups[unixGroup]) { - var xlen = unixgroups[unixGroup].length; - if (xlen == len) { - var html = projectIcon(projects[projectId].name) + projectLink(projectId) + ": " + len + " committers"; - if (unixgroups[unixGroup+'-pmc']) { - html += ", " + unixgroups[unixGroup+'-pmc'].length + " PMC members"; - } - appendLiInnerHTML(ul,html); - } - } - } - } - - obj.appendChild(ul); -} - -function renderProjectsByCommittee() { - var obj = document.getElementById('list'); - obj.innerHTML = ""; - var projectsSorted = sortProjects(); - - var dcount = {}; - for (var committee in committees) { - dcount[committee] = 0; - } - for (var project in projects) { - project = projects[project]; - if (committees[project.pmc]) { - dcount[project.pmc]++; - } - } - - // Construct pmc list - var ul = document.createElement('ul'); - - var lpmc; - for (lpmc in committees) { - var c = dcount[lpmc]; - var li = document.createElement('li'); - var cul = document.createElement('ul'); - if (c == 0 && lpmc != 'labs') { - appendLiInnerHTML(cul, projectIcon(committees[lpmc].name) + "<a href='project.html?" + lpmc + "'>" + committees[lpmc].name + "</a>"); - c = 1; - } else { - var i; - for (i in projectsSorted) { - i = projectsSorted[i]; - var project = projects[i]; - if (committees[project.pmc]) { - var xlpmc = project.pmc; - if (xlpmc == lpmc) { - if (project.doap) { - appendLiInnerHTML(cul, projectIcon(project.name) + projectLink(i)); - } else { - c=0; - if (xlpmc == 'incubator') { - appendLiInnerHTML(cul, "<b>"+ project.name + ": please <a href='https://projects.apache.org/create.html'>create a DOAP</a> file</b>"); - } else { - appendLiInnerHTML(cul, "<b>Please <a href='https://projects.apache.org/create.html'>create a DOAP</a> file</b>"); - } - } - } - } - } - } - li.innerHTML = "<h3>" + committeeIcon() + "<a id='" + lpmc + "' href='committee.html?"+ lpmc + "'>" + committees[lpmc].name + " Committee</a>" + (c!=1?(" (" + c + ")"):"") + (c>0?":": "") + "</h3>"; - li.appendChild(cul); - ul.appendChild(li); - } - - obj.appendChild(ul); - - if (location.hash.length > 1) { - setTimeout(function() { location.href = location.href;}, 250); - } -} - -function buildProjectsList() { - var cat = document.location.search.substr(1); - var types = { - 'name': [ 'name', renderProjectsByName ], - 'language': [ 'language', renderProjectsByLanguage ], - 'category': [ 'category', renderProjectsByCategory ], - 'number': [ 'number of committers', renderProjectsByNumber ], - 'pmc': [ 'Committee', renderProjectsByCommittee ], - 'committee': [ 'Committee', renderProjectsByCommittee ] - } - if ((cat.length == 0) || !(cat in types)) { - cat = "name"; - } - - var type = types[cat]; - var obj = document.getElementById('title'); - obj.innerHTML = "<h1>Projects by " + type[0] + ":</h1>" - - preloadEverything(type[1]); -} - -function sortCommittees() { - var committeesSortedX = []; - var committeesSorted = []; - for (i in committees) { - committeesSortedX.push([i, committees[i].name.toLowerCase()]); - } - // compare names (already lower-cased) - committeesSortedX.sort(function(a,b) { return a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0 }) - for (i in committeesSortedX) { - committeesSorted.push(committeesSortedX[i][0]); - } - return committeesSorted; -} - -function renderCommitteesByName() { - var obj = document.getElementById('list'); - obj.innerHTML = ""; - var committeesSorted = sortCommittees(); - - // Committee list - var ul = document.createElement('ul'); - var i; - for (i in committeesSorted) { - appendLiInnerHTML(ul, committeeIcon() + committeeLink(committeesSorted[i])); - } - obj.appendChild(ul); -} - -function renderCommitteesByDate() { - var obj = document.getElementById('list'); - obj.innerHTML = ""; - - var dates = []; - var dcount = {}; - var i; - for (i in committees) { - var date = committees[i].established; - if (dates.indexOf(date) < 0) { - dates.push(date); - dcount[date] = 0; - } - dcount[date]++; - } - dates.sort() - - // Construct date list - var ul = document.createElement('ul'); - - var l; - for (l in dates) { - var date = dates[l]; - var li = document.createElement('li'); - li.innerHTML = "<h3><a id='" + date + "'>" + date + " (" + dcount[date] + ")</a>:</h3>"; - var cul = document.createElement('ul'); - var i; - for (i in committeesByName) { - i = committeesByName[i]; - if (i.established == date) { - appendLiInnerHTML(cul, committeeIcon() + committeeLink(i.id)); - } - } - li.appendChild(cul); - ul.appendChild(li); - } - - obj.appendChild(ul); - - if (location.hash.length > 1) { - setTimeout(function() { location.href = location.href;}, 250); - } -} - -function buildCommitteesList() { - var cat = document.location.search.substr(1); - var types = { - 'name': [ 'name', renderCommitteesByName ], - 'date': [ 'founding date', renderCommitteesByDate ] - } - if ((cat.length == 0) || !(cat in types)) { - cat = "name"; - } - - var type = types[cat]; - var obj = document.getElementById('title'); - obj.innerHTML = "<h1>Committees by " + type[0] + ":</h1>" - - preloadEverything(type[1]); -} - - -// Rendering project list using DataTables instead of the usual stuff: -function buildProjectListAsTable(json) { - var arr = []; - for (p in projects) { - var project = projects[p]; - - // Get name of PMC - var pmc = committees[project.pmc] ? committees[project.pmc].name : "Unknown"; - - // Get project type - var type = "Sub-Project"; - var shortp = p.split("-")[0]; - if (unixgroups[shortp]) { - type = "TLP"; - if ((!committeesByName[project.name] && committees[project.pmc]) || project.name.match(/incubating/i)) { - type = "Sub-project"; - } - } else { - type = "Retired"; - } - - if (project.podling || project.name.match(/incubating/i)) { - type = "Podling"; - pmc = "Apache Incubator"; - } - - // Programming language - var pl = project['programming-language'] ? project['programming-language'] : "Unknown"; - - // Shove the result into a row - arr.push([ p, project.name, type, pmc, pl, project.category]) - } - - // Construct the data table - $('#contents').html( '<table cellpadding="0" cellspacing="0" border="0" class="display" id="projectlist"></table>' ); - - $('#projectlist').dataTable( { - "data": arr, - "columns": [ - { "title": "ID", "visible": false }, - { "title": "Name" }, - { "title": "Type" }, - { "title": "PMC" }, - { "title": "Programming Language(s)" }, - { "title": "Category" } - ], - "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull) { - jQuery(nRow).attr('id', aData[0]); - jQuery(nRow).css("cursor", "pointer"); - return nRow; - } - } ); - - $('#projectlist tbody').on('click', 'tr', function () { - var name = $(this).attr('id'); - location.href = "project.html?" + name - } ); -} - - -function isCommittee(name) { - return committeesByName[name]; -} - -// ------------ Front page rendering ------------\\ - -function update_id(id, val) { - var obj = document.getElementById(id); - if (obj) { - obj.innerHTML = val; - } -} -function renderFrontPage() { - var numcommittees = 0; - var i; - for (i in committees) numcommittees++; - var curPodlings = 0; - for (i in podlings) curPodlings++; - - // The projects list contains 1 entry for each podling, as well as 1 entry for each DOAP. - // Each podling relates to a single project, but a PMC may have one or more projects. - // However not all projects may have registered DOAPs. - // In order to find these missing projects, we need to find projects that have not registered DOAPs - - var projectsWithDoaps = {}; // ids of projects which have registered DOAPS - var numProjects = 0; // total projects run by active PMCs - for (j in projects) { - i = projects[j]; - projectsWithDoaps[i.pmc] = 1; // which projects have got DOAPs - if (i.pmc != 'attic' && i.pmc != 'incubator') { - numProjects++; // found a project run by an active PMC (not podling or retired) - } - } - var numprojectsWithDoaps = 0; // how many projects have registered DOAPs - for (i in projectsWithDoaps) numprojectsWithDoaps++; - numProjects += (numcommittees - numprojectsWithDoaps); // Add in projects without DOAPs - var obj = document.getElementById('details'); - if (urlErrors.length > 0) { - obj.innerHTML += "<p><span style='color: red'><b>Warning: could not load: "+urlErrors.join(', ')+"</b></span></p>" - } - update_id('loading', ''); // clear the loading messages - // N.B. These ids must agree with the 'details' div in index.html - update_id('committees', numcommittees); - update_id('projects', numProjects); - update_id('podlings', curPodlings); - renderCommitteeEvolution(); - renderPodlingsEvolution(); - renderLanguageChart(); -} - - -// ------------ Chart functions ------------\\ - -function htmlListTooltip(date,name,values) { - return '<div style="padding:8px 8px 8px 8px;"><b>' + date + '</b>' - + '<br/><b>' + values.length + '</b> ' + name + ((values.length > 1) ? 's:':':') - + ((values.length > 0) ? '<br/>- '+values.join('<br/>- '):'') - + '</div>'; -} - -function renderCommitteeEvolution() { - var evo = {}; // 'year-month' -> { established: [], retired: [] } - // init evo with empty content for the whole period - var maxYear = new Date().getFullYear(); - for (var year = 1995; year <= maxYear; year++) { - var maxMonth = (year < maxYear) ? 12 : (new Date().getMonth() + 1); - for (var month = 1; month <= maxMonth; month++) { - var key = year + '-' + (month < 10 ? '0':'') + month; - evo[key] = { 'established': [], 'retired': [] }; - } - } - // add current committees - var c; - for (c in committees) { - c = committees[c]; - if (evo[c.established]) { - evo[c.established]['established'].push(c); - } else { - console.log(c.id + ": " + c.established + " is off(?!)"); - } - } - // add retired committees - for (c in retiredCommittees) { - c = retiredCommittees[c]; - if (evo[c.established] && evo[c.retired]) { - evo[c.established]['established'].push(c); - evo[c.retired]['retired'].push(c); - - } else { - console.log(c.id + ": " + c.established + " or " + c.retired + " is off(?!)"); - } - } - // compute data - var data = []; - var cur = 0; - var d; - for (d in evo) { - var established = evo[d]['established']; - var retired = evo[d]['retired']; - var estDisplay = []; - for (c in established) { - c = established[c]; - estDisplay.push(c.name + ((c.id in retiredCommittees) ? ' (retired ' + retiredCommittees[c.id]['retired'] + ')':'')); - } - var retDisplay = []; - for (c in retired) { - c = retired[c]; - retDisplay.push(c.name + ' (established ' + c['established'] + ')'); - } - cur += established.length - retired.length; - data.push([d, established.length, htmlListTooltip(d, 'new committee', estDisplay), -1*retired.length, htmlListTooltip(d, 'retired committee', retDisplay), cur]); - } - //narr.sort(function(a,b) { return (b[1] - a[1]) }); - var dataTable = new google.visualization.DataTable(); - dataTable.addColumn('string', 'Month'); - dataTable.addColumn('number', "New committees"); - dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); - dataTable.addColumn('number', "Retired committees"); - dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); - dataTable.addColumn('number', 'Current committees'); - - dataTable.addRows(data); - - var options = { - title: "Evolution of Committees (also called PMCs or Top Level Projects)", - isStacked: true, - height: 320, - width: 1160, - seriesType: "bars", - backgroundColor: 'transparent', - series: {2: {type: "line", targetAxisIndex: 1}}, - tooltip: {isHtml: true}, - vAxes:[ - {title: 'Change in states', ticks: [-3,0,3,6,9]}, - {title: 'Current number of committees'} - ] - }; - var div = document.createElement('div'); - document.getElementById('details').appendChild(div); - var chart = new google.visualization.ComboChart(div); - chart.draw(dataTable, options); -} - -function renderPodlingsEvolution(obj) { - var evo = {}; // 'year-month' -> { started: [], graduated: [], retired: [] } - // init evo with empty content for the whole period - var maxYear = new Date().getFullYear(); - for (var year = 2003; year <= maxYear; year++) { - var maxMonth = (year < maxYear) ? 12 : (new Date().getMonth() + 1); - for (var month = 1; month <= maxMonth; month++) { - var key = year + '-' + (month < 10 ? '0':'') + month; - evo[key] = { 'started': [], 'graduated': [], 'retired': [] }; - } - } - // add current podlings - var p; - for (p in podlings) { - p = podlings[p]; - if (p['podling']) { - evo[p.started]['started'].push(p); - } - } - // add podlings history - for (p in podlingsHistory) { - p = podlingsHistory[p]; - evo[p.started]['started'].push(p); - evo[p.ended][p.status].push(p); - } - // compute data - var data = []; - var cur = 0; - var d; - for (d in evo) { - var started = evo[d]['started']; - var graduated = evo[d]['graduated']; - var retired = evo[d]['retired']; - var startedDisplay = []; - for (p in started) { - p = started[p]; - startedDisplay.push(p.name + (p['ended'] ? ' (' + p.status + ' ' + p.ended + ')':'')); - } - var graduatedDisplay = []; - for (p in graduated) { - p = graduated[p]; - graduatedDisplay.push(p.name + ' (started ' + p.started + ')'); - } - var retiredDisplay = []; - for (p in retired) { - p = retired[p]; - retiredDisplay.push(p.name + ' (started ' + p.started + ')'); - } - cur += started.length - graduated.length - retired.length; - data.push([d, started.length, htmlListTooltip(d, 'new podling', startedDisplay), - -1*graduated.length, htmlListTooltip(d, 'graduated podling', graduatedDisplay), - -1*retired.length, htmlListTooltip(d, 'retired podling', retiredDisplay), cur]); - } - //narr.sort(function(a,b) { return (b[1] - a[1]) }); - var dataTable = new google.visualization.DataTable(); - dataTable.addColumn('string', 'Month'); - dataTable.addColumn('number', "New podlings"); - dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); - dataTable.addColumn('number', "Graduated podlings"); - dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); - dataTable.addColumn('number', "Retired podlings"); - dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}}); - dataTable.addColumn('number', 'Current podlings'); - - dataTable.addRows(data); - - var coptions = { - title: "Evolution of Incubating projects ('podlings')", - isStacked: true, - height: 320, - width: 1160, - seriesType: "bars", - backgroundColor: 'transparent', - colors: ['#3366cc', '#109618', '#dc3912', '#ff9900'], - series: {3: {type: "line", targetAxisIndex: 1}}, - tooltip: {isHtml: true}, - vAxes: [ - {title: 'Change in states', ticks: [-6,-3,0,3,6]}, - {title: 'Current number of podlings'} - ] - }; - var div = document.createElement('div'); - document.getElementById('details').appendChild(div); - var chart = new google.visualization.ComboChart(div); - chart.draw(dataTable, coptions); -} - -function renderLanguageChart() { - var obj = document.getElementById('details'); - - // Languages - var lingos = []; - var lcount = {}; - for (var i in projects) { - i = projects[i]; - if (i['programming-language']) { - var a = i['programming-language'].split(/,\s*/); - for (var x in a) { - x = a[x]; - if (lingos.indexOf(x) < 0) { - lingos.push(x); - lcount[x] = 0; - } - lcount[x]++; - } - } - } - - - var narr = []; - for (i in lingos) { - var lang = lingos[i]; - narr.push([lang, lcount[lang], 'Click here to view declared projects using ' + lang]); - } - narr.sort(function(a,b) { return (b[1] - a[1]) }); - - var data = new google.visualization.DataTable(); - data.addColumn('string', 'Language'); - data.addColumn('number', "Projects using it"); - data.addColumn({type: 'string', role: 'tooltip'}); - data.addRows(narr); - - var options = { - title: 'Language distribution (click on a language to view declared projects using it)', - height: 400, - backgroundColor: 'transparent' - }; - - var chartDiv = document.createElement('div'); - var chart = new google.visualization.PieChart(chartDiv); - obj.appendChild(chartDiv); - - function selectHandlerLanguage() { - var selectedItem = chart.getSelection()[0]; - if (selectedItem) { - var value = data.getValue(selectedItem.row, 0); - location.href = "projects.html?language#" + value; - } - } - google.visualization.events.addListener(chart, 'select', selectHandlerLanguage); - chart.draw(data, options); - - - // Categories - var cats = []; - var ccount = {}; - for (i in projects) { - i = projects[i]; - if (i.category) { - var a = i.category.split(/,\s*/); - for (x in a) { - if (cats.indexOf(a[x]) < 0) { - cats.push(a[x]); - ccount[a[x]] = 0; - } - ccount[a[x]]++; - } - } - } - - - var carr = []; - for (i in cats) { - var cat = cats[i]; - carr.push([cat, ccount[cat], 'Click here to view declared projects in the ' + cat + ' category']) - } - carr.sort(function(a,b) { return (b[1] - a[1]) }); - - - var data2 = new google.visualization.DataTable(); - data2.addColumn('string', 'Category'); - data2.addColumn('number', "Projects"); - data2.addColumn({type: 'string', role: 'tooltip'}); - data2.addRows(carr); - - var options2 = { - title: 'Categories (click on a category to view declared projects within it)', - height: 400, - backgroundColor: 'transparent' - }; - - var chartDiv2 = document.createElement('div'); - var chart2 = new google.visualization.PieChart(chartDiv2); - obj.appendChild(chartDiv2); - - - function selectHandlerCategory() { - var selectedItem = chart2.getSelection()[0]; - if (selectedItem) { - var value = data2.getValue(selectedItem.row, 0); - location.href = "projects.html?category#" + value; - } - } - google.visualization.events.addListener(chart2, 'select', selectHandlerCategory); - chart2.draw(data2, options2); -} - -// This is the entry point from index.html and about.html - -function buildFrontPage() { - renderFrontPage({}, null) -} - - - -// ------- Account creation chart function -------- \\ - -function drawAccountCreation(json) { - var i; - var j; - var narr = []; - var cdata = new google.visualization.DataTable(); - cdata.addColumn('string', 'Date'); - cdata.addColumn('number', 'New committers'); - cdata.addColumn('number', 'Total number of committers'); - var max = 0; - var jsort = []; - for (j in json) { - jsort.push(j); - } - - jsort.sort(); - var c = 0; - for (i in jsort) { - i = jsort[i]; - var entry = json[i]; - var arr = i.split("-"); - var d = new Date(parseInt(arr[0]), parseInt(arr[1]), 1); - c += entry; - narr.push([i, entry, c]); - max = (max < entry) ? entry : max; - } - cdata.addRows(narr); - - var options = { - title: ('Account creation timeline'), - backgroundColor: 'transparent', - height: 320, - width: 1160, - vAxes:[ - - {title: 'New accounts', titleTextStyle: {color: '#0000DD'}, maxValue: Math.max(200,max)}, - {title: 'Total number of accounts', titleTextStyle: {color: '#DD0000'}}, - - ], - series: { - 1: {type: "line", pointSize:3, lineWidth: 3, targetAxisIndex: 1}, - 0: {type: "bars", targetAxisIndex: 0} - }, - seriesType: "bars", - tooltip: {isHtml: true} - }; - - var obj = document.createElement('div'); - obj.style = "float: left; width: 1160px; height: 450px;"; - obj.setAttribute("id", 'accountchart'); - var contents = document.getElementById('contents'); - contents.innerHTML = "<h1>Timelines</h1>"; - contents.appendChild(obj); - - var chart = new google.visualization.ComboChart(obj); - chart.draw(cdata, options); -} - -// called by timelines.html - -function buildTimelines() { - GetAsyncJSON("json/foundation/accounts-evolution.json", null, drawAccountCreation); -} - - -// called by timelines2.html - -function buildTimelines2() { - GetAsyncJSON("json/foundation/accounts-evolution2.json", null, drawAccountCreation); -} - - -// ------------ Search feature for the site ------------\\ - -function searchProjects(str) { - var obj = document.getElementById('contents'); - - str = str.toLowerCase(); - var hits = {}; - var hitssorted = []; - - // Search committees - for (p in projects) { - var project = projects[p]; - for (key in project) { - if (typeof project[key] == "string") { - var val = project[key].toLowerCase(); - if (val.indexOf(str) >= 0 && val.substr(0,1) != "{") { - if (!hits[p]) { - hits[p] = []; - } - var estr = new RegExp(str, "i"); - hits[p].push({ - 'key': key, - 'val': project[key].replace(estr, function(a) { return "<u style='color: #963;'>"+a+"</u>"}, "img") - }); - if (hitssorted.indexOf(p) < 0) { - hitssorted.push(p); - } - } - } - } - } - - var title = "Search results for '" + str + "' (" + hitssorted.length + "):"; - obj.innerHTML = ""; - var h2 = document.createElement('h2'); - h2.appendChild(document.createTextNode(title)); - obj.appendChild(h2); - hitssorted.sort(function(a,b) { return hits[b].length - hits[a].length }); - var ul = document.createElement('ul'); - - var h; - for (h in hitssorted) { - h = hitssorted[h]; - var project = hits[h]; - var html = "<h4><a href='project.html?" + h + "'>" + projects[h].name + "</a> (" + project.length + " hit(s)):</h4>"; - for (x in project) { - html += "<blockquote><b>" + project[x].key + ":</b> " + project[x].val + "</blockquote>"; - } - appendLiInnerHTML(ul,html); - } - if (hitssorted.length == 0) { - obj.innerHTML += "No search results found"; - } - obj.appendChild(ul); -} - - - -// Key press monitoring for search field -function checkKeyPress(e, txt) { - if (!e) e = window.event; - var keyCode = e.keyCode || e.which; - if (keyCode == '13'){ - searchProjects(txt.value); - } -} - - -// ------------ Weave functions ------------\\ - -function weaveById(list,mapById) { - for (var i in list) { - var o = list[i]; - mapById[o.id] = o; - } -} - -function fixProjectName(project) { - // fix attic and incubator project names if necessary - if (project.pmc == "attic") { - project.name += " (in the Attic)"; - } else if ((project.pmc == "incubator") && !project.name.match(/incubating/i)) { - project.name += " (Incubating)"; - } - return project; -} - -// Add content by id to projects -function weaveInProjects(json, pfx) { - if (!pfx) { pfx='' } - for (p in json) { - // Since podlings are loaded first, DOAPs take precedence - projects[pfx+p] = fixProjectName(json[p]); - } -} - -function weaveInRetiredCommittees(json) { - weaveById(json, retiredCommittees); - var p; - var projectsPmcs = {}; - for (p in projects) { - projectsPmcs[projects[p].pmc] = p; - } -} - -function setCommittees(json, state) { - weaveById(json, committees); - var c; - for (c in json) { - c = json[c]; - // committeesByName = { name -> committee } - committeesByName[c.name] = c; - } - if (state) { - state(); - } -} - -// Render releases using datatables -function renderReleases(releases) { - var arr = []; - for (p in releases) { - var releasedata = releases[p]; - - for (filename in releasedata) { - var date = releasedata[filename]; - // Shove the result into a row - arr.push([ p, p, date, filename]); - } - } - - // Construct the data table - $('#contents2').html( '<table cellpadding="0" cellspacing="0" border="0" class="display" id="releases"></table>' ); - - $('#releases').dataTable( { - "data": arr, - "columns": [ - { "title": "ID", "visible": false }, - { "title": "Name" }, - { "title": "Date" }, - { "title": "Release name" } - ], - "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull) { - jQuery(nRow).attr('id', aData[0]); - jQuery(nRow).css("cursor", "pointer"); - return nRow; - } - } ); - - $('#releases tbody').on('click', 'tr', function () { - var name = $(this).attr('id').replace("incubator-","incubator/"); - location.href = "https://www.apache.org/dist/" + name; - } ); -} - -// Generate a 'Link to here' pop-up marker -function linkToHere(id) { - return "<a class='sectionlink' href='#"+id+"' title='Link to here'>¶</a>" -} - -// Called by releases.html - -function buildReleases() { - GetAsyncJSON("json/foundation/releases.json?" + Math.random(), null, renderReleases); -} - -// ------------ Async data fetching ------------\\ -// This function is the starter of every page, and preloads the needed files -// before running the final page renderer. This is roughly 1 mb of JSON, but as -// it gets cached after first run, it's not really a major issue. - -function preloadEverything(callback) { - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith - if (!String.prototype.startsWith) { - String.prototype.startsWith = function (searchString, position) { - position = position || 0; - return this.substr(position, searchString.length) === searchString; - }; - } - GetAsyncJSONArray([ - ["json/foundation/committees.json", "committees", setCommittees], - ["json/foundation/groups.json", "groups", function(json) { unixgroups = json; }], - ["json/foundation/people_name.json", "people", function(json) { people = json; }], - ["json/foundation/podlings.json", "podlings", function(json) { podlings = json; weaveInProjects(json,'incubator-')}], // do this first - ["json/foundation/projects.json", "projects", weaveInProjects], // so can replace with DOAP data where it exists - ["json/foundation/committees-retired.json", "retired committees", weaveInRetiredCommittees], - ["json/foundation/podlings-history.json", "podlings history", function(json) { podlingsHistory = json; }], - ["json/foundation/repositories.json", "repositories", function(json) { repositories = json; }] - ], - callback); -} +/* + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +*/ + +// ----- Global hashes used throughout the script ------ \\ + +var people = {}; // committer -> name lookups +var unixgroups = {}; // unix (ldap) groups (project -> committers lookup) +var committees = {}; // id -> committee info (chair, established, group, homepage, id, name, reporting, shortdesc) (current committees) +var committeesByName = {}; // name -> committee info +var retiredCommittees = {}; // retired committees information: id -> committee info (established, retired, homepage, id, name) +var projects = {}; // Projects +var podlings = {}; // current podlings +var podlingsHistory = {}; // Podlings history (now graduated or retired) +var repositories = {}; // source repositories id -> url + +// --------- Global helpers ----------- \\ + +function includeJs(jsFilePath) { + var js = document.createElement("script"); + + js.type = "text/javascript"; + js.src = jsFilePath; + + document.head.appendChild(js); +} + +includeJs("js/underscore-min.js"); + +function GetAsyncJSON(theUrl, xstate, callback) { + var xmlHttp = null; + if (window.XMLHttpRequest) { + xmlHttp = new XMLHttpRequest(); + } else { + xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); + } + xmlHttp.open("GET", theUrl, true); + xmlHttp.send(null); + xmlHttp.onreadystatechange = function(state) { + if (xmlHttp.readyState == 4 && xmlHttp.status == 200 || xmlHttp.status == 404) { + if (callback) { + if (xmlHttp.status == 404) { + callback({}, xstate); + } else { + callback(JSON.parse(xmlHttp.responseText), xstate); + } + } + } + } +} + +var urlErrors = [] +var fetchCount = 0; +// Fetch an array of URLs, each with their description and own callback plus a final callback +// Used to fetch everything before rendering a page that relies on multiple JSON sources. +function GetAsyncJSONArray(urls, finalCallback) { + var obj = document.getElementById('progress'); + if (fetchCount == 0 ) { + fetchCount = urls.length; + } + + if (urls.length > 0) { + var a = urls.shift(); + var URL = a[0]; + var desc = a[1]; + var cb = a[2]; + var xmlHttp = null; + if (window.XMLHttpRequest) { + xmlHttp = new XMLHttpRequest(); + } else { + xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); + } + + if (obj) { obj.innerHTML = "loading file #" + ( fetchCount - urls.length ) + " / " + fetchCount + "<br>" + desc } + + xmlHttp.open("GET", URL, true); + xmlHttp.onreadystatechange = function(state) { + if (xmlHttp.readyState == 4) { + if (cb) { + if (xmlHttp.status == 200) { + cb(JSON.parse(xmlHttp.responseText)); + } else { + urlErrors.push(URL) + cb({}); + } + } + GetAsyncJSONArray(urls, finalCallback); + } + } + xmlHttp.send(null); + } + else { + if (obj) { obj.innerHTML = "building page content..." } + finalCallback(); + } +} + +// See project_editor.js (not currently used) + +// ------------ Project information page ------------\\ + +function linkCommitterIndex(cid) { + var fullname = people[cid]; + var cl = isMember(cid) ? "member" : "committer"; + return "<a class='" + cl + "' title='" + cid + "' href='https://home.apache.org/phonebook.html?uid=" + cid + "' target='_blank'>" + fullname + "</a>"; +} + +function appendElementWithInnerHTML(obj,type,html) { + var child = document.createElement(type); + child.innerHTML = html; + obj.appendChild(child); + return child; +} + +function appendLiInnerHTML(ul,html) { + return appendElementWithInnerHTML(ul,'li',html); +} + +function projectIdToUnixGroup(projectId, pmcName) { + // Rerig the unix name and committee id + var unixgroup = projectId.split("-")[0]; + /* + Temp hack for podling names. TODO need to sort out generated names + */ + if (projectId.indexOf("incubator-") === 0) { + unixgroup = projectId.split("-")[1] + } + // special cases + if (unixgroup === "empire") unixgroup = "empire-db"; + if (unixgroup === "community") unixgroup = "comdev"; + if (pmcName === "attic") { + unixgroup = "attic"; + } + return unixgroup; +} + +function renderProjectPage(project, projectId) { + var obj = document.getElementById('contents'); + + if ((!project || !project.name) && projects[projectId]) { + // no DOAP file but known project: podling (loaded from podlings.json) + project = projects[projectId]; + } + if (!project || !project.name) { + obj.innerHTML = "<h2>Sorry, I don't have any information available about this project</h2>"; + return; + } + + fixProjectName(project); + var isIncubating = project && (project.podling || (project.pmc == 'incubator')); + + var unixgroup = projectIdToUnixGroup(projectId, project && project.pmc); + + var committeeId = isIncubating ? 'incubator' : unixgroup; + if (!committees[unixgroup]) { + // at least one committee has a unix group that is different from committee id: webservices (group=ws), see parsecommittees.py#group_ids + // search instead of hard-coding the currently known case + for (p in committees) { + if (committees[p].group == unixgroup) { + committeeId = p; + break; + } + } + } + var committee = committees[committeeId]; + if (!committee) { + obj.innerHTML = "<h2>Cannot find the PMC '" + committeeId + "' for this project. Check the DOAP is correct.</h2>"; + return; + } + + // Start by splitting the name, thus fetching the root name of the project, and not the sub-project. + var description = ""; + if (project) { + if (!_.isEmpty(project.description)) { + description = project.description; + } else if (!_.isEmpty(project.shortdesc)) { + description = project.shortdesc; + } else if (!_.isEmpty(committee.shortdesc)) { + description = committee.shortdesc; + } else { + description = "No description available"; + } + } + + // Title + description + obj.innerHTML = "<h1>" + project.name + " <font size='-1'>(a project managed by the <a href='committee.html?" + committeeId + "'>" + committee.name + " Committee</a>)</font></h1>"; + + // project description + appendElementWithInnerHTML(obj,'p',description.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a) { return "<p>"+a+"</p>"})); + + var ul = document.createElement('ul'); + + // Base data + appendElementWithInnerHTML(obj,'h4',"Project base data:"); + + if (project.description && project.shortdesc) { + appendLiInnerHTML(ul, "<b>Short description:</b> " + project.shortdesc); + } + + // Categories + if (project.category) { + var arr = project.category.split(/,\s*/); + var pls = ""; + for (i in arr) { + var cat = arr[i]; + // categories are downcased so fix up the anchor + pls += "<a href='projects.html?category#" + cat.toLowerCase() + "'>" + cat + "</a> "; + } + appendLiInnerHTML(ul, "<b>Category:</b> " + pls); + } + + // Website + if (project.homepage) { + appendLiInnerHTML(ul, "<b>Website:</b> <a href='" + project.homepage + "' target='_blank'>" + project.homepage + "</a>"); + } + if (isIncubating) { + appendLiInnerHTML(ul, "<b>Project status:</b> <span class='ppodling'>Incubating</span>"); + } else if (committeeId != 'attic') { + appendLiInnerHTML(ul, "<b>Project status:</b> <span class='pactive'>Active</span>"); + } else { + appendLiInnerHTML(ul, "<b>Project status:</b> <span class='pretired'>Retired</span>"); + } + + // Committers + if (isIncubating && unixgroups[unixgroup]) { + var commitl = []; + var commitgroup = unixgroups[unixgroup]; + for (i in commitgroup) { + commitl.push(linkCommitterIndex(commitgroup[i])); + } + appendLiInnerHTML(ul, "<b>Committers (" + commitgroup.length + "):</b> <blockquote>" + commitl.join(", ") + "</blockquote>"); + } + + if (project.implements) { + var stds = document.createElement('ul'); + var impl; + for (impl in project.implements) { + impl = project.implements[impl]; + var std = ""; + if (impl.body) { + std += impl.body + ' '; + } + if (impl.id) { + std += "<a href='" + impl.url + "'>" + impl.id + "</a>: " + impl.title; + } else { + std += "<a href='" + impl.url + "'>" + impl.title + "</a>"; + } + appendLiInnerHTML(stds, std); + } + appendLiInnerHTML(ul, "<b>Implemented standards</b>").appendChild(stds); + } + + // doap/rdf + if (project.doap) { + appendLiInnerHTML(ul, "<b>Project data file:</b> <a href='" + project.doap + "' target='_blank'>DOAP RDF Source</a> (<a href='json/projects/" + projectId + ".json'>generated json</a>)"); + } else { + appendLiInnerHTML(ul, "<b>Project data file:</b> no <a href='https://projects.apache.org/create.html'>DOAP file</a> available"); + } + // maintainer + if (project.maintainer) { + var mt; + var maintainers = ""; + for (mt in project.maintainer) { + mt = project.maintainer[mt]; + if (mt.mbox) { + var id = mt.mbox; + id = id.substr(id.indexOf(':') + 1); + id = id.substr(0, id.indexOf('@')); + if (people[id]) { + maintainers += linkCommitterIndex(id) + " "; + } else { + maintainers += "<a href='" + mt.mbox + "'>" + mt.name + "</a> "; + } + } else { + maintainers += mt.name + " "; + } + } + appendLiInnerHTML(ul, "<b>Project data maintainer(s):</b> " + maintainers); + } + + obj.appendChild(ul); + + // Code data + appendElementWithInnerHTML(obj,'h4',"Development:"); + ul = document.createElement('ul'); + + if (project['programming-language']) { + var pl = project['programming-language']; + var arr = pl.split(/,\s*/); + var pls = ""; + for (i in arr) { + pls += "<a href='projects.html?language#" + arr[i] + "'>" + arr[i] + "</a> "; + } + appendLiInnerHTML(ul, "<b>Programming language:</b> " + pls); + } + + if (project['bug-database']) { + var bd = project['bug-database']; + var arr = bd.split(/,\s*/); + var bds = ""; + for (i in arr) { + bds += "<a href='" + arr[i] + "'>" + arr[i] + "</a> "; + } + appendLiInnerHTML(ul, "<b>Bug-tracking:</b> " + bds); + } + + if (project['mailing-list']) { + var ml = project['mailing-list']; + var xml = ml; + // email instead of link? + if (ml.match(/@/)) { + xml = "mailto:" + ml; + } + appendLiInnerHTML(ul, "<b>Mailing list(s):</b> <a href='" + xml + "'>" + ml + "</a>"); + } + + // repositories + if (project.repository) { + var r; + for (r in project.repository) { + r = project.repository[r]; + if (r.indexOf("svn") > 0) { + appendLiInnerHTML(ul, "<b>Subversion repository:</b> <a target=*_blank' href='" + r + "'>" + r + "</a>"); + } else if (r.indexOf("git") > 0) { + appendLiInnerHTML(ul, "<b>Git repository:</b> <a target=*_blank' href='" + r + "'>" + r + "</a>"); + } else { + appendLiInnerHTML(ul, "<b>Repository:</b> <a target=*_blank' href='" + r + "'>" + r + "</a>"); + } + } + } + + obj.appendChild(ul); + + // releases + appendElementWithInnerHTML(obj,'h4',"Releases <font size='-2'>(from DOAP)</font>:"); + ul = document.createElement('ul'); + if (project['download-page']) { + appendLiInnerHTML(ul, "<b>Download:</b> <a href='" + project['download-page'] + "' target='_blank'>" + project['download-page'] + "</a>"); + } + if (project.release) { + project.release.sort(function(a,b){// reverse date order (most recent first) + var ac = a.created ? a.created : '1970-01-01'; + var bc = b.created ? b.created : '1970-01-01'; + if(ac < bc) return 1; + if(ac > bc) return -1; + return 0;}); + var r; + for (r in project.release) { + r = project.release[r]; + var html = "<b>" + (r.revision ? r.revision : r.version) + "</b>"; + html += " (" + (r.created ? r.created : 'unknown') + ")"; + appendLiInnerHTML(ul, html + ": " + r.name); + } + } + obj.appendChild(ul); +} + + +function buildProjectPage() { + var projectId = document.location.search.substr(1); + GetAsyncJSON("json/projects/" + projectId + ".json?" + Math.random(), projectId, renderProjectPage) +} + +// extract committee name from repo name +function repoToCommittee(reponame) { + if (reponame.startsWith('empire-db')) { + return 'empire-db'; + } + return reponame.split('-')[0]; +} + +function renderCommitteePage(committeeId) { + var obj = document.getElementById('contents'); + + if (!committees[committeeId]) { + obj.innerHTML = "<h2>Sorry, I don't have any information available about this committee</h2>"; + return; + } + + var unixgroup = committeeId; // there are probably a few exceptions... + var committee = committees[committeeId]; + + obj.innerHTML = "<h1>" + committee.name + " Committee <font size='-2'>(also called PMC or <a href='https://www.apache.org/#projects-list' target='_blank'>Top Level Project</a>)</font></h1>"; + + var description; + if (!_.isEmpty(committee.shortdesc)) { + description = committee.shortdesc; + } else { + description = "Missing from https://whimsy.apache.org/public/ committee-info.json"; + } + + appendElementWithInnerHTML(obj, 'h4', "Description <font size='-2'>(from <a href='https://whimsy.apache.org/public/' target='_blank'>committee-info</a>)</a>:"); + + appendElementWithInnerHTML(obj,'p',description.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a) { return "<p>"+a+"</p>"})); + + appendElementWithInnerHTML(obj, 'h4', "Charter <font size='-2'>(from PMC data file)</a>:"); + + var charter; + if (!_.isEmpty(committee.charter)) { + charter = committee.charter; + } else { + charter = "Missing"; + } + + appendElementWithInnerHTML(obj,'p',charter.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a) { return "<p>"+a+"</p>"})); + + var ul = document.createElement('ul'); + + appendElementWithInnerHTML(obj, 'h4', "Committee data:"); + + appendLiInnerHTML(ul, "<b>Website:</b> <a href='" + committee.homepage + "' target='_blank'>" + committee.homepage + "</a>"); + + appendLiInnerHTML(ul, "<b>Committee established:</b> " + committee.established); + + // VP + appendLiInnerHTML(ul, "<b>PMC Chair:</b> " + linkCommitterIndex(committee.chair)); + + // Reporting cycle + var cycles = ["every month", "January, April, July, October", "February, May, August, November", "March, June, September, December"]; + var minutes = committee.name.substr("Apache ".length).replace(/ /g, '_'); + // does not work for APR and Logging Services currently + if (committeeId == 'apr') { + minutes = 'Apr'; + } else if (committeeId == 'logging') { + minutes = 'Logging'; + } + appendLiInnerHTML(ul, "<b>Reporting cycle:</b> " + cycles[committee.reporting] + ", see <a href='https://whimsy.apache.org/board/minutes/" + minutes + ".html' target='_blank'>minutes</a>"); + + // PMC + if (committee.roster) { // check we have the data + var pmcl = []; + for (i in committee.roster) { + pmcl.push(linkCommitterIndex(i)); + } + if (pmcl.length > 0) { + appendLiInnerHTML(ul, "<b>PMC Roster <font size='-1'>(from <a href='https://whimsy.apache.org/public/' target='_blank'>committee-info</a>; updated daily)</font> (" + pmcl.length + "):</b> <blockquote>" + pmcl.join(", ") + "</blockquote>"); + } else { + appendLiInnerHTML(ul, "<b>PMC Roster not found in committee-info.txt (check that Section 3 has been updated)</b>"); + } + } + + // Committers + if (unixgroups[unixgroup]) { + var commitl = []; + var commitgroup = unixgroups[unixgroup]; + for (i in commitgroup) { + commitl.push(linkCommitterIndex(commitgroup[i])); + } + appendLiInnerHTML(ul, "<b>Committers; updated daily (" + commitgroup.length + "):</b> <blockquote>" + commitl.join(", ") + "</blockquote>"); + } + + // rdf + if (committee.rdf) { + appendLiInnerHTML(ul, "<b>PMC data file:</b> <a href='" + committee.rdf + "' target='_blank'>RDF Source</a>"); + } + + obj.appendChild(ul); + + var subprojects = []; + for (p in projects) { + if (projects[p].pmc == committeeId) { + subprojects.push(p); + } + } + if (subprojects.length == 0) { + if (committeeId != 'labs') { + // if a committee did not declare any project (DOAP), consider there is a default one with the id of the committee + // only Labs doesn't manage projects + subprojects.push({ 'id': committeeId, 'name': committee.name, 'pmc': committeeId }); + } + } else { + appendElementWithInnerHTML(obj, 'h4', "Projects managed by this Committee:"); + + ul = document.createElement('ul'); + for (var p in subprojects.sort()) { + p = subprojects[p]; + appendLiInnerHTML(ul, projectLink(p)); + } + obj.appendChild(ul); + } + + var repos = []; + for (var r in repositories) { + if (committeeId == repoToCommittee(r)) { + repos.push(r); + } + } + if (repos.length > 0) { + appendElementWithInnerHTML(obj, 'h4', + "Source repositories managed by this Committee <font size='-2'>" + + "(from <a href='https://gitbox.apache.org/repositories.json'>ASF Git repos</a>" + + " and <a href='https://svn.apache.org/repos/asf/'>ASF SVN repos</a>)</font>:"); + + ul = document.createElement('ul'); + for (var r in repos.sort()) { + r = repos[r]; + var url = repositories[r]; + appendLiInnerHTML(ul, r + ": <a href='" + url + "'>" + url + "</a>"); + } + obj.appendChild(ul); + } +} + +function buildCommitteePage() { + var committeeId = document.location.search.substr(1); + renderCommitteePage(committeeId); +} + + +// ------------ Projects listing ------------\\ + +function camelCase(str) { + return str.replace(/^([a-z])(.+)$/, function(c,a,b) { return a.toUpperCase() + b.toLowerCase() } ); +} + +function committeeIcon() { + return "<img src='images/committee.png' title='Committee' style='vertical-align: middle; padding: 2px;'/> "; +} + +function projectIcon() { + return "<img src='images/project.png' title='Project' style='vertical-align: middle; padding: 2px;'/> " +} + +function committeeLink(id) { + var committee = committees[id]; + return "<a href='committee.html?" + id + "'>" + committee.name + "</a> - " + committee.shortdesc; +} + +function projectLink(id) { + var project = projects[id]; + if (!project) { + // not project id: perhaps committee id + project = committees[id]; + } + return "<a href='project.html?" + id + "'>" + project.name + "</a>"; +} + +function isMember(id) { + return _(unixgroups['member']).indexOf(id) >= 0; +} + +function sortProjects() { + var projectsSortedX = []; + var projectsSorted = []; + for (i in projects) { + projectsSortedX.push([i, projects[i].name.toLowerCase()]); + } + // compare names (already lower-cased) + projectsSortedX.sort(function(a,b) { return a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0 }) + for (i in projectsSortedX) { + projectsSorted.push(projectsSortedX[i][0]); + } + return projectsSorted; +} + +function renderProjectsByName() { + var obj = document.getElementById('list'); + obj.innerHTML = ""; + var projectsSorted = sortProjects(); + + // Project list + var ul = document.createElement('ul'); + for (var i in projectsSorted) { + var project = projectsSorted[i]; + appendLiInnerHTML(ul, projectIcon(projects[project].name) + projectLink(project)); + } + obj.appendChild(ul); +} + +function renderProjectsByLanguage() { + var obj = document.getElementById('list'); + obj.innerHTML = ""; + var projectsSorted = sortProjects(); + + // Compile Language array + var lingos = []; + var lcount = {}; + var i; + var x; + for (i in projects) { + if (projects[i]['programming-language']) { + var a = projects[i]['programming-language'].split(/,\s*/); + for (x in a) { + a[x] = camelCase(a[x]); + if (lingos.indexOf(a[x]) < 0) { + lingos.push(a[x]); + lcount[a[x]] = 0; + } + lcount[a[x]]++; + } + } + } + + // Construct language list + lingos.sort(); + var toc = document.createElement('p'); + var toch = document.createElement('h3'); + toch.textContent = 'TOC'; + toc.appendChild(toch); + var ul = document.createElement('ul'); + + var l; + for (l in lingos) { + var lang = lingos[l]; + var tocitem = document.createElement('a'); + tocitem.href="#" + lang; + tocitem.innerHTML=lang; + if (l > 0) { // divider + toc.appendChild(document.createTextNode(', ')); + } + toc.appendChild(tocitem); + var li = document.createElement('li'); + li.innerHTML = "<h3><a id='" + lang + "'>" + lang + " (" + lcount[lang] + ")</a>"+linkToHere(lang)+":</h3>"; + var cul = document.createElement('ul'); + for (i in projectsSorted) { + i = projectsSorted[i]; + if (projects[i]['programming-language']) { + var a = projects[i]['programming-language'].split(/,\s*/); + for (x in a) { + // Use same capitalisation as the language list + if (camelCase(a[x]) == lang) { + appendLiInnerHTML(cul, projectIcon(projects[i].name) + projectLink(i)); + } + } + } + } + li.appendChild(cul); + ul.appendChild(li); + } + + obj.appendChild(toc); + obj.appendChild(ul); + + if (location.hash.length > 1) { + setTimeout(function() { location.href = location.href;}, 250); + } +} + +function renderProjectsByCategory() { + var obj = document.getElementById('list'); + obj.innerHTML = ""; + var projectsSorted = sortProjects(); + + var cats = []; + var ccount = {}; + var i; + for (i in projects) { + if (projects[i].category) { + var a = projects[i].category.split(/,\s*/); + var x; + for (x in a) { + x = a[x].toLowerCase(); // must agree with downcase below + if (cats.indexOf(x) < 0) { + cats.push(x); + ccount[x] = 0; + } + ccount[x]++; + } + } + } + cats.sort(); + + // Construct category list + var toc = document.createElement('p'); + var toch = document.createElement('h3'); + toch.textContent = 'TOC'; + toc.appendChild(toch); + var ul = document.createElement('ul'); + + var l; + for (l in cats) { + var cat = cats[l]; + var tocitem = document.createElement('a'); + tocitem.href="#" + cat; + tocitem.innerHTML=cat; + if (l > 0) { // divider + toc.appendChild(document.createTextNode(', ')); + } + toc.appendChild(tocitem); + var li = document.createElement('li'); + li.innerHTML = "<h3><a id='" + cat + "'>" + cat + " (" + ccount[cat] + ")</a>"+linkToHere(cat)+":</h3>"; + var cul = document.createElement('ul'); + for (i in projectsSorted) { + i = projectsSorted[i]; + var project = projects[i]; + if (project.category) { + var a = project.category.split(/,\s*/); + for (x in a) { + x = a[x].toLowerCase(); // must agree with downcase above + if (x == cat) { + appendLiInnerHTML(cul, projectIcon(project.name) + projectLink(i)); + } + } + } + } + li.appendChild(cul); + ul.appendChild(li); + } + + obj.appendChild(toc); + obj.appendChild(ul); + + if (location.hash.length > 1) { + setTimeout(function() { location.href = location.href;}, 250); + } +} + +function renderProjectsByNumber() { + var obj = document.getElementById('list'); + obj.innerHTML = ""; + var projectsSorted = sortProjects(); + + var lens = []; + var lcount = {}; + for (projectId in projects) { + let unixGroup = projectIdToUnixGroup(projectId); + if (unixgroups[unixGroup] && projectId !== 'incubator') { + let len = unixgroups[unixGroup].length; + if (lens.indexOf(len) < 0) { + lens.push(len); + lcount[len] = 0; + } + lcount[len]++; + } + } + lens.sort(function(a,b) { return b - a }); + + // Construct date list + var ul = document.createElement('ul'); + + for (l in lens) { + var len = lens[l]; + var projectId; + for (projectId in projectsSorted) { + projectId = projectsSorted[projectId]; + let unixGroup = projectIdToUnixGroup(projectId); + if (unixgroups[unixGroup]) { + var xlen = unixgroups[unixGroup].length; + if (xlen == len) { + var html = projectIcon(projects[projectId].name) + projectLink(projectId) + ": " + len + " committers"; + if (unixgroups[unixGroup+'-pmc']) { + html += ", " + unixgroups[unixGroup+'-pmc'].length + " PMC members"; + } + appendLiInnerHTML(ul,html); + } + } + } + } + + obj.appendChild(ul); +} + +function renderProjectsByCommittee() { + var obj = document.getElementById('list'); + obj.innerHTML = ""; + var projectsSorted = sortProjects(); + + var dcount = {}; + for (var committee in committees) { + dcount[committee] = 0; + } + for (var project in projects) { + project = projects[project]; + if (committees[project.pmc]) { + dcount[project.pmc]++; + } + } + + // Construct pmc list + var ul = document.createElement('ul'); + + var lpmc; + for (lpmc in committees) { + var c = dcount[lpmc]; + var li = document.createElement('li'); + var cul = document.createElement('ul'); + if (c == 0 && lpmc != 'labs') { + appendLiInnerHTML(cul, projectIcon(committees[lpmc].name) + "<a href='project.html?" + lpmc + "'>" + committees[lpmc].name + "</a>"); + c = 1; + } else { + var i; + for (i in projectsSorted) { + i = projectsSorted[i]; + var project = projects[i]; + if (committees[project.pmc]) { + var xlpmc = project.pmc; + if (xlpmc == lpmc) { + if (project.doap) { + appendLiInnerHTML(cul, projectIcon(project.name) + projectLink(i)); + } else { + c=0; + if (xlpmc == 'incubator') { + appendLiInnerHTML(cul, "<b>"+ project.name + ": please <a href='https://projects.apache.org/create.html'>create a DOAP</a> file</b>"); + } else { + appendLiInnerHTML(cul, "<b>Please <a href='https://projects.apache.org/create.html'>create a DOAP</a> file</b>"); + } + } + } + } + } + } + li.innerHTML = "<h3>" + committeeIcon() + "<a id='" + lpmc + "' href='committee.html?"+ lpmc + "'>" + committees[lpmc].name + " Committee</a>" + (c!=1?(" (" + c + ")"):"") + (c>0?":": "") + "</h3>"; + li.appendChild(cul); + ul.appendChild(li); + } + + obj.appendChild(ul); + + if (location.hash.length > 1) { + setTimeout(function() { location.href = location.href;}, 250); + } +} + +function buildProjectsList() { + var cat = document.location.search.substr(1); + var types = { + 'name': [ 'name', renderProjectsByName ], + 'language': [ 'language', renderProjectsByLanguage ], + 'category': [ 'category', renderProjectsByCategory ], + 'number': [ 'number of committers', renderProjectsByNumber ], + 'pmc': [ 'Committee', renderProjectsByCommittee ], + 'committee': [ 'Committee', renderProjectsByCommittee ] + } + if ((cat.length == 0) || !(cat in types)) { + cat = "name"; + } + + var type = types[cat]; + var obj = document.getElementById('title'); + obj.innerHTML = "<h1>Projects by " + type[0] + ":</h1>" + + preloadEverything(type[1]); +} + +function sortCommittees() { + var committeesSortedX = []; + var committeesSorted = []; + for (i in committees) { + committeesSortedX.push([i, committees[i].name.toLowerCase()]); + } + // compare names (already lower-cased)
[... 1102 lines stripped ...]
