diff --git a/.gitignore b/.gitignore index d9bc85e..1db6620 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ node_modules # generated by gulp feed.xml projects.html +locations.json diff --git a/gulpfile.js b/gulpfile.js index 819d22e..369881a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,27 +1,29 @@ var gulp = require('gulp'); -var jsonlint = require("gulp-jsonlint"); +var jsonlint = require('gulp-jsonlint'); var transform = require('gulp-transform'); var rename = require('gulp-rename'); -var jsonSchema = require("gulp-json-schema"); +var jsonSchema = require('gulp-json-schema'); -var hashCode = function (str) { +const LOCATION_RESOLVER_BASE_URL = 'https://nominatim.openstreetmap.org'; + +https: var hashCode = function (str) { var hash = 0; if (str.length === 0) return hash; for (var i = 0; i < str.length; i++) { var char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; + hash = (hash << 5) - hash + char; hash = hash & hash; // Convert to 32bit integer } return Math.abs(hash); }; var entityMap = { - "&": "&", - "<": "<", - ">": ">", + '&': '&', + '<': '<', + '>': '>', '"': '"', "'": ''', - "/": '/' + '/': '/', }; /** @@ -42,129 +44,301 @@ function escapeHTML(string) { * if a structure ist not defined. */ gulp.task('validate-events', function () { - - return gulp.src("./events.json") + return gulp + .src('./events.json') .pipe(jsonlint()) .pipe(jsonlint.failOnError()) - .pipe(jsonSchema({ - schema: "./events.schema.json", - missing: "warn", - emitError: true, - })); + .pipe( + jsonSchema({ + schema: './events.schema.json', + missing: 'warn', + emitError: true, + }) + ); }); /** * Generate the xml file based on the events. */ -gulp.task('generate-xml', gulp.series('validate-events', function () { - - return gulp.src("./events.json") - .pipe(jsonlint()) - .pipe(jsonlint.failOnError()) - .pipe(jsonlint.reporter()) - .pipe(transform(function (contents) { - var today = new Date(); - today = new Date(today.setHours(0, 0, 0)); - var events = JSON.parse(contents); - console.log('found ' + events.length + ' events'); - var xml = []; - xml.push(""); - xml.push(""); - xml.push(""); - xml.push("Hackergarten Events"); - xml.push("https://hackergarten.net"); - xml.push("en-en"); - for (var i = 0; i < events.length; i++) { - var event = events[i]; - var hash = hashCode(event.date + event.location); - if (new Date(event.date) >= today) { - // This code is duplicated in eventlist.js - if (event.title && event.location) { // old JSON format without venue and address - event.title = "on " + event.date + " at " + event.title + " in " + event.location; - } else if (event.venue && event.address) { // new JSON format with venue and address - event.title = "on " + event.date + " at " + event.venue + ", " + event.address; - } else { // fallback with title and date - event.title += " on " + event.date; +gulp.task( + 'generate-xml', + gulp.series('validate-events', function () { + return gulp + .src('./events.json') + .pipe(jsonlint()) + .pipe(jsonlint.failOnError()) + .pipe(jsonlint.reporter()) + .pipe( + transform(function (contents) { + var today = new Date(); + today = new Date(today.setHours(0, 0, 0)); + var events = JSON.parse(contents); + console.log('found ' + events.length + ' events'); + var xml = []; + xml.push(''); + xml.push(''); + xml.push(''); + xml.push('Hackergarten Events'); + xml.push('https://hackergarten.net'); + xml.push('en-en'); + for (var i = 0; i < events.length; i++) { + var event = events[i]; + var hash = hashCode(event.date + event.location); + if (new Date(event.date) >= today) { + // This code is duplicated in eventlist.js + if (event.title && event.location) { + // old JSON format without venue and address + event.title = + 'on ' + + event.date + + ' at ' + + event.title + + ' in ' + + event.location; + } else if (event.venue && event.address) { + // new JSON format with venue and address + event.title = + 'on ' + + event.date + + ' at ' + + event.venue + + ', ' + + event.address; + } else { + // fallback with title and date + event.title += ' on ' + event.date; + } + xml.push(''); + xml.push( + '' + + escapeHTML('Hackergarten ' + event.title) + + '' + ); + xml.push( + 'https://hackergarten.net/#event-' + + hash + + '' + ); + xml.push('' + hash + ''); + xml.push('\n'); + } } - xml.push(""); - xml.push("" + escapeHTML("Hackergarten " + event.title) + ""); - xml.push("https://hackergarten.net/#event-" + hash + ""); - xml.push("" + hash + ""); - xml.push("\n"); - } - } - xml.push(""); - xml.push(""); - return xml.join(''); - })) - .pipe(rename("feed.xml")) - .pipe(gulp.dest('.')); -})); + xml.push(''); + xml.push(''); + return xml.join(''); + }) + ) + .pipe(rename('feed.xml')) + .pipe(gulp.dest('.')); + }) +); /** - * Generate the HTML file containing the top contributed projects. + * Generate the location json file based on the events. */ -gulp.task('generate-projects', gulp.series('validate-events', function () { +gulp.task( + 'generate-locations', + gulp.series('validate-events', function () { + return gulp + .src('./events.json') + .pipe(jsonlint()) + .pipe(jsonlint.failOnError()) + .pipe(jsonlint.reporter()) + .pipe( + transform(async function (contents) { + var oneYearAgo = new Date(); + oneYearAgo.setDate(oneYearAgo.getDate() - 52 * 7); + var events = JSON.parse(contents); + var recentEvents = events.filter(function (event) { + return new Date(event.date) > oneYearAgo; + }); + var uniqueLocations = {}; + for (var i = 0; i < recentEvents.length; i++) { + if (!uniqueLocations[recentEvents[i].address]) { + uniqueLocations[recentEvents[i].address] = []; + } + uniqueLocations[recentEvents[i].address].push( + recentEvents[i] + ); + } + var locations = []; + for (var address in uniqueLocations) { + const event = uniqueLocations[address][0]; - let projects = new Map(); + const { lat, long } = await searchLocation( + event.address + ); - function addProject(project, url) { - let value = projects.get(project) || {url: url, count: 0}; - value.count++; - projects.set(project, value); + locations.push({ + title: 'Hackergarten City', + sponsors: [event.venue], + link: getLink(event), + lat, + long, + }); + } + console.log( + 'Found', + events.length, + 'events at', + locations.length, + 'unique locations.' + ); + return JSON.stringify(locations); + }) + ) + .pipe(rename('locations.json')) + .pipe(gulp.dest('.')); + }) +); + +function getLink(event) { + if (event.links && event.links[0]) { + var url = event.links[0].url; + if (url && url.includes('meetup.com')) { + return url.split('events')[0]; + } } +} - return gulp.src("./events.json") - .pipe(jsonlint()) - .pipe(jsonlint.failOnError()) - .pipe(jsonlint.reporter()) - .pipe(transform(function(contents) { - let events = JSON.parse(contents); - let html = []; - html.push(''); - html.push(''); - html.push(''); - html.push(''); - html.push('Top Contributed Projects'); - html.push(''); - html.push(''); - html.push(''); - html.push(''); - html.push(''); - html.push(''); - for (let i = 0; i < events.length; i++) { - let event = events[i]; - if (event.achievements) { - for (let j = 0; j < event.achievements.length; j++) { - let achievement = event.achievements[j]; - if (achievement.url) { - if (achievement.url.startsWith("https://github.com/")) { - let urlParts = achievement.url.split("/"); - let project = urlParts[4]; - let url = 'https://github.com/' + urlParts[3] + '/' + urlParts[4]; - if (project !== undefined) addProject(project, url); +async function searchLocation(query) { + const url = `${LOCATION_RESOLVER_BASE_URL}/search?q=${encodeURIComponent( + query + )}&format=json&addressdetails=1&limit=1`; + + try { + const response = await fetch(url, { + method: 'GET', + headers: { + userAgent: 'i bims', + }, + }); + + if (!response.ok) { + console.log( + "An error occured, but I don't know why", + await response.text() + ); + } + + const [first, ...data] = await response.json(); + + return { + lat: first.lat, + long: first.lon, + }; + } catch (error) { + return { error: 'Error fetching data', details: error.message }; + } +} + +/** + * Generate the HTML file containing the top contributed projects. + */ +gulp.task( + 'generate-projects', + gulp.series('validate-events', function () { + let projects = new Map(); + + function addProject(project, url) { + let value = projects.get(project) || { url: url, count: 0 }; + value.count++; + projects.set(project, value); + } + + return gulp + .src('./events.json') + .pipe(jsonlint()) + .pipe(jsonlint.failOnError()) + .pipe(jsonlint.reporter()) + .pipe( + transform(function (contents) { + let events = JSON.parse(contents); + let html = []; + html.push(''); + html.push(''); + html.push(''); + html.push(''); + html.push('Top Contributed Projects'); + html.push( + '' + ); + html.push( + '' + ); + html.push( + '' + ); + html.push(''); + html.push(''); + html.push( + '
#ProjectCount
' + ); + for (let i = 0; i < events.length; i++) { + let event = events[i]; + if (event.achievements) { + for ( + let j = 0; + j < event.achievements.length; + j++ + ) { + let achievement = event.achievements[j]; + if (achievement.url) { + if ( + achievement.url.startsWith( + 'https://github.com/' + ) + ) { + let urlParts = + achievement.url.split('/'); + let project = urlParts[4]; + let url = + 'https://github.com/' + + urlParts[3] + + '/' + + urlParts[4]; + if (project !== undefined) + addProject(project, url); + } + } } } } - } - } - let sortedProjects = new Map([...projects].sort((a, b) => - a[1].count === b[1].count ? 0 : a[1].count < b[1].count ? 1 : -1)); - let i = 1; - sortedProjects.forEach((value, project) => { - html.push(''); - html.push(''); - html.push(''); - html.push(''); - html.push(''); - }); - html.push('
#ProjectCount
' + i++ + '' + project + '' + value.count + '
'); - html.push(''); - html.push(''); - return html.join(''); - })) - .pipe(rename("projects.html")) - .pipe(gulp.dest('.')); -})); - -gulp.task('default', gulp.series('generate-xml', 'generate-projects')); + let sortedProjects = new Map( + [...projects].sort((a, b) => + a[1].count === b[1].count + ? 0 + : a[1].count < b[1].count + ? 1 + : -1 + ) + ); + let i = 1; + sortedProjects.forEach((value, project) => { + html.push(''); + html.push('' + i++ + ''); + html.push( + '' + + project + + '' + ); + html.push('' + value.count + ''); + html.push(''); + }); + html.push(''); + html.push(''); + html.push(''); + return html.join(''); + }) + ) + .pipe(rename('projects.html')) + .pipe(gulp.dest('.')); + }) +); + +gulp.task( + 'default', + gulp.series('generate-xml', 'generate-projects', 'generate-locations') +); diff --git a/index.html b/index.html index 1cab5f0..89f6c26 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,6 @@ + @@ -20,9 +21,7 @@ - + @@ -38,213 +37,212 @@ - -
-
+ +
+
- - + + +
-
- -
- -
-
- -
-
Next Events
-
-
    - -
  • - Hack #{{allEventlistLength + $index}} {{event.title}} -
    → {{event.status.title}}
    - -
  • - -
- more coming up ...look at the Join section below... + +
+ +
+
+ +
+
Next Events
+
+
    + +
  • + Hack #{{allEventlistLength + $index}} {{event.title}} +
    → {{event.status.title}}
    + +
  • + +
+ more coming up ...look at the Join section below... +
-
- -
-
About
-
- Hackergarten is a craftmen's workshop, classroom, a laboratory, a social circle, a writing group, a playground, and an artist's studio. Our goal is to create something that others can use; whether it be working software, improved documentation, or better educational materials. Our intent is to end each meeting with a patch or similar contribution submitted to an open and public project. Membership is open to anyone willing to contribute their time. + +
+
About
+
+ Hackergarten is a craftmen's workshop, classroom, a laboratory, a social circle, a writing group, a playground, and an artist's studio. Our goal is to create something that others can use; whether it be working software, improved documentation, or better educational materials. Our intent is to end each meeting with a patch or similar contribution submitted to an open and public project. Membership is open to anyone willing to contribute their time. +
-
- -
-
Locations
-
-
+ +
+
Locations
+
+
+
-
- -
-
Past Events
-
-
    -
  • - Hack #{{pastEventlistLength - $index}} {{event.title}} -
    → {{event.status.title}}
    - - -
  • -
-
- + +
+
Past Events
+
+
    +
  • + Hack #{{pastEventlistLength - $index}} {{event.title}} +
    → {{event.status.title}}
    + + +
  • +
+
+ +
-
-
-
Open Hackergarten Issues from GitHub (max. 30)
-
+
+
Open Hackergarten Issues from GitHub (max. 30)
+
+
+
- -
-
-
Downloads
-
-

- Please note: To use the Hackergarten logo, you must agree to the - license terms, which you can also find on - this page. -

-
- - -
- +
+
Downloads
+
+

+ Please note: To use the Hackergarten logo, you must agree to the + license terms, which you can also find on + this page. +

+
+ + +
+ +
-
-
+
- -
-