summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json100
-rw-r--r--[-rwxr-xr-x]package.json3
-rwxr-xr-xpublic/css/style.css16
-rwxr-xr-xroutes.js43
-rwxr-xr-xviews/event.handlebars169
5 files changed, 197 insertions, 134 deletions
diff --git a/package-lock.json b/package-lock.json
index 4422ce0..2232874 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2706,9 +2706,9 @@
}
},
"handlebars": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz",
- "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==",
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
+ "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
"requires": {
"neo-async": "^2.6.0",
"optimist": "^0.6.1",
@@ -2818,6 +2818,24 @@
"rrule": "2.4.1"
}
},
+ "ical-generator": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/ical-generator/-/ical-generator-1.9.2.tgz",
+ "integrity": "sha512-z3OLKk/b9TbyOLKOIMpjLJ3u7gq/tXPNsH5uOz+Ai3sqn2kcpjFlZUafKrlduwZn3Xu3fV5WqZ2dddZFrhQTfg==",
+ "requires": {
+ "moment-timezone": "^0.5.27"
+ },
+ "dependencies": {
+ "moment-timezone": {
+ "version": "0.5.27",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz",
+ "integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==",
+ "requires": {
+ "moment": ">= 2.9.0"
+ }
+ }
+ }
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -3327,9 +3345,9 @@
}
},
"kareem": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.0.tgz",
- "integrity": "sha512-6hHxsp9e6zQU8nXsP+02HGWXwTkOEw6IROhF2ZA28cYbUk4eJ6QbtZvdqZOdD9YPKghG3apk5eOCvs+tLl3lRg=="
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
+ "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
},
"keypairs": {
"version": "1.2.14",
@@ -3509,12 +3527,6 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
- "memory-pager": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
- "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
- "optional": true
- },
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -3642,38 +3654,26 @@
}
},
"mongodb": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.7.tgz",
- "integrity": "sha512-2YdWrdf1PJgxcCrT1tWoL6nHuk6hCxhddAAaEh8QJL231ci4+P9FLyqopbTm2Z2sAU6mhCri+wd9r1hOcHdoMw==",
- "requires": {
- "mongodb-core": "3.2.7",
- "safe-buffer": "^5.1.2"
- }
- },
- "mongodb-core": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.7.tgz",
- "integrity": "sha512-WypKdLxFNPOH/Jy6i9z47IjG2wIldA54iDZBmHMINcgKOUcWJh8og+Wix76oGd7EyYkHJKssQ2FAOw5Su/n4XQ==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.2.tgz",
+ "integrity": "sha512-fqJt3iywelk4yKu/lfwQg163Bjpo5zDKhXiohycvon4iQHbrfflSAz9AIlRE6496Pm/dQKQK5bMigdVo2s6gBg==",
"requires": {
"bson": "^1.1.1",
"require_optional": "^1.0.1",
- "safe-buffer": "^5.1.2",
- "saslprep": "^1.0.0"
+ "safe-buffer": "^5.1.2"
}
},
"mongoose": {
- "version": "5.6.6",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.6.6.tgz",
- "integrity": "sha512-5uecJSyl2TwbGM9vJteP4C54zsQL6qllq1qe/JPGO3oqIWcK/PnzCL91E0gfPH5VVpvWGX+6PafNYmU3NK8S7w==",
+ "version": "5.7.5",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.7.5.tgz",
+ "integrity": "sha512-BZ4FxtnbTurc/wcm/hLltLdI4IDxo4nsE0D9q58YymTdZwreNzwO62CcjVtaHhmr8HmJtOInp2W/T12FZaMf8g==",
"requires": {
- "async": "2.6.2",
"bson": "~1.1.1",
- "kareem": "2.3.0",
- "mongodb": "3.2.7",
- "mongodb-core": "3.2.7",
+ "kareem": "2.3.1",
+ "mongodb": "3.3.2",
"mongoose-legacy-pluralize": "1.0.2",
"mpath": "0.6.0",
- "mquery": "3.2.1",
+ "mquery": "3.2.2",
"ms": "2.1.2",
"regexp-clone": "1.0.0",
"safe-buffer": "5.1.2",
@@ -3681,14 +3681,6 @@
"sliced": "1.0.1"
},
"dependencies": {
- "async": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz",
- "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==",
- "requires": {
- "lodash": "^4.17.11"
- }
- },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -3707,9 +3699,9 @@
"integrity": "sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw=="
},
"mquery": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.1.tgz",
- "integrity": "sha512-kY/K8QToZWTTocm0U+r8rqcJCp5PRl6e8tPmoDs5OeSO3DInZE2rAL6AYH+V406JTo8305LdASOQcxRDqHojyw==",
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz",
+ "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==",
"requires": {
"bluebird": "3.5.1",
"debug": "3.1.0",
@@ -4524,15 +4516,6 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
- "saslprep": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
- "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
- "optional": true,
- "requires": {
- "sparse-bitfield": "^3.0.3"
- }
- },
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@@ -4805,15 +4788,6 @@
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"dev": true
},
- "sparse-bitfield": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
- "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
- "optional": true,
- "requires": {
- "memory-pager": "^1.0.2"
- }
- },
"split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
diff --git a/package.json b/package.json
index fff1c2d..bce9181 100755..100644
--- a/package.json
+++ b/package.json
@@ -23,11 +23,12 @@
"greenlock": "^2.6.7",
"greenlock-express": "^2.6.7",
"ical": "^0.6.0",
+ "ical-generator": "^1.9.2",
"jimp": "^0.6.0",
"jsonwebtoken": "^8.4.0",
"marked": "^0.7.0",
"moment-timezone": "^0.5.26",
- "mongoose": "^5.3.13",
+ "mongoose": "^5.7.5",
"multer": "^1.4.1",
"node-schedule": "^1.3.1",
"randomstring": "^1.1.5",
diff --git a/public/css/style.css b/public/css/style.css
index 4085875..e55f07a 100755
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -115,6 +115,11 @@ body, html {
text-transform:capitalize;
}
+#eventActions {
+ padding-left: 0;
+ margin-top: 1rem;
+}
+
/*
.location, .eventLink {
display: flex;
@@ -162,10 +167,6 @@ body, html {
margin-left: 5px;
}
-.eventInformationAction:not(#copyEventLink) {
- margin-top: 0.25rem;
-}
-
.commentContainer {
background: #fafafa;
border-radius: 5px;
@@ -308,6 +309,13 @@ body, html {
}
}
+@media (min-width: 1120px) {
+ #eventActions {
+ margin-top: 0;
+ padding-left: 1rem;
+ }
+}
+
@media (min-width: 577px) {
#sidebar {
border-right: 2px solid #e0e0e0;
diff --git a/routes.js b/routes.js
index ee723e2..f2bca02 100755
--- a/routes.js
+++ b/routes.js
@@ -62,6 +62,11 @@ render_plain = function () {
}
const ical = require('ical');
+const icalGenerator = require('ical-generator');
+const cal = icalGenerator({
+ domain: 'gath.io',
+ name: 'Gathio'
+});
const sgMail = require('@sendgrid/mail');
@@ -408,6 +413,42 @@ router.get('/group/:eventGroupID', (req, res) => {
});
})
+router.get('/exportevent/:eventID', (req, res) => {
+ Event.findOne({
+ id: req.params.eventID
+ })
+ .populate('eventGroup')
+ .then((event) => {
+ if (event) {
+ const icalEvent = cal.createEvent({
+ start: moment.tz(event.start, event.timezone),
+ end: moment.tz(event.start, event.timezone),
+ timezone: event.timezone,
+ timestamp: moment(),
+ summary: event.name,
+ description: event.description,
+ organizer: {
+ name: event.hostName ? event.hostName : "Anonymous",
+ email: event.creatorEmail
+ },
+ location: event.location,
+ url: 'https://gath.io/' + event.id
+ });
+
+ let string = cal.toString();
+ console.log(string)
+ res.send(string);
+ }
+ })
+ .catch((err) => {
+ addToLog("exportEvent", "error", "Attempt to export event " + req.params.eventID + " failed with error: " + err);
+ console.log(err)
+ res.status(404);
+ res.render('404', { url: req.url });
+ return;
+ });
+})
+
// BACKEND ROUTES
//router.post('/login',
@@ -529,7 +570,7 @@ router.post('/importevent', (req, res) => {
image: '',
creatorEmail: creatorEmail,
url: '',
- hostName: importedEventData.organizer ? importedEventData.organizer.params.CN : "",
+ hostName: importedEventData.organizer ? importedEventData.organizer.params.CN.replace(/["]+/g, '') : "",
viewPassword: '',
editPassword: '',
editToken: editToken,
diff --git a/views/event.handlebars b/views/event.handlebars
index 4d0cf28..7a57783 100755
--- a/views/event.handlebars
+++ b/views/event.handlebars
@@ -8,82 +8,92 @@
<h3 id="eventName">{{eventData.name}}</h3>
</div>
{{#if editingEnabled}}
- <div class="col-lg-2 ml-2 edit-buttons">
+ <div class="col-lg-3 ml-2 edit-buttons">
<div class="btn-group" role="group" aria-label="Event controls">
- <button type="button" id="editEvent" class="btn btn-success" data-toggle="modal" data-target="#editModal" {{#if eventHasConcluded}}disabled{{/if}}><i class="fas fa-edit"></i></button>
- <button type="button" id="deleteEvent" class="btn btn-danger" data-toggle="modal" data-target="#deleteModal"><i class="fas fa-trash"></i></button>
+ <button type="button" id="editEvent" class="btn btn-success" data-toggle="modal" data-target="#editModal" {{#if eventHasConcluded}}disabled{{/if}}><i class="fas fa-edit"></i> Edit</button>
+ <button type="button" id="deleteEvent" class="btn btn-danger" data-toggle="modal" data-target="#deleteModal"><i class="fas fa-trash"></i> Delete</button>
</div>
</div>
{{/if}}
</div>
-<div class="card mt-4 mb-4">
- <div class="card-body">
- <ul class="fa-ul eventInformation">
- <li>
- <span class="fa-li">
- <i class="fas fa-map-marker-alt"></i>
- </span>
- {{eventData.location}}<br />
- <a target="_blank" href="http://maps.google.com/?q={{parsedLocation}}" class="eventInformationAction btn btn-outline-secondary btn-sm">
+<div class="container my-4 pr-0">
+ <div class="row">
+ <div class="col-lg-9 card">
+ <div class="card-body">
+ <ul class="fa-ul eventInformation">
+ <li>
+ <span class="fa-li">
+ <i class="fas fa-map-marker-alt"></i>
+ </span>
+ {{eventData.location}}
+ </li>
+ <li>
+ <span class="fa-li">
+ <i class="fas fa-fw fa-calendar-day"></i>
+ </span>
+ {{{displayDate}}}
+ <br>
+ <span class="text-muted">
+ {{#if eventHasBegun}}{{#unless eventHasConcluded}}Started {{else}}Ended {{/unless}}{{/if}}{{fromNow}}
+ </span>
+ </li>
+ {{#if eventHasHost}}
+ <li>
+ <span class="fa-li">
+ <i class="fas fa-fw fa-user-circle"></i>
+ </span>
+ <span class="text-muted">Hosted by</span> {{eventData.hostName}}
+ </li>
+ {{/if}}
+ {{#if eventData.eventGroup}}
+ <li>
+ <span class="fa-li">
+ <i class="fas fa-fw fa-calendar-alt"></i>
+ </span>
+ <span class="text-muted">Part of</span> <a href="/group/{{eventData.eventGroup.id}}">{{eventData.eventGroup.name}}</a>
+ </li>
+ {{/if}}
+ {{#if eventData.url}}
+ <li>
+ <span class="fa-li">
+ <i class="fas fa-fw fa-link"></i>
+ </span>
+ <a href="{{eventData.url}}">
+ {{eventData.url}}
+ </a>
+ </li>
+ {{/if}}
+ <li>
+ <span class="fa-li">
+ <i class="fas fa-fw fa-share-square"></i>
+ </span>
+ <a href="https://gath.io/{{eventData.id}}">gath.io/{{eventData.id}}</a>
+ <button type="button" id="copyEventLink" class="eventInformationAction btn btn-outline-secondary btn-sm" data-clipboard-text="https://gath.io/{{eventData.id}}">
+ <i class="fas fa-copy"></i> Copy
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="col-lg-3" id="eventActions">
+ <aside class="btn-group-vertical d-flex" role="group" aria-label="Event actions">
+ <a href="http://www.google.com/calendar/event?action=TEMPLATE&dates={{parsedStart}}%2F{{parsedEnd}}&text={{escapedName}}&location={{parsedLocation}}&ctz={{timezone}}" class="btn btn-outline-secondary btn-sm">
+ <i class="far fa-calendar-plus"></i> Add to Google Calendar
+ </a>
+ <button type="button" id="exportICS" class="btn btn-outline-secondary btn-sm" data-event-id="{{eventData.id}}">
+ <i class="fas fa-download"></i> Export as ICS
+ </button>
+ <a target="_blank" href="http://maps.google.com/?q={{parsedLocation}}" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-map-marked"></i> Show on Google Maps
</a>
- &nbsp;
- <a target="_blank" href="https://www.openstreetmap.org/search?query={{parsedLocation}}" class="eventInformationAction btn btn-outline-secondary btn-sm">
+ <a target="_blank" href="https://www.openstreetmap.org/search?query={{parsedLocation}}" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-map-marked"></i> Show on OpenStreetMap
</a>
- </li>
- <li>
- <span class="fa-li">
- <i class="fas fa-fw fa-calendar-day"></i>
- </span>
- {{{displayDate}}}
- <br>
- <span class="text-muted">
- {{#if eventHasBegun}}{{#unless eventHasConcluded}}Started {{else}}Ended {{/unless}}{{/if}}{{fromNow}}
- </span>
- <br />
- <a href="http://www.google.com/calendar/event?action=TEMPLATE&dates={{parsedStart}}%2F{{parsedEnd}}&text={{escapedName}}&location={{parsedLocation}}&ctz={{timezone}}" class="eventInformationAction btn btn-outline-secondary btn-sm">
- <i class="far fa-calendar-plus"></i> Add to Google Calendar
- </a>
- </li>
- {{#if eventHasHost}}
- <li>
- <span class="fa-li">
- <i class="fas fa-fw fa-user-circle"></i>
- </span>
- <span class="text-muted">Hosted by</span> {{eventData.hostName}}
- </li>
- {{/if}}
- {{#if eventData.eventGroup}}
- <li>
- <span class="fa-li">
- <i class="fas fa-fw fa-calendar-alt"></i>
- </span>
- <span class="text-muted">Part of</span> <a href="/group/{{eventData.eventGroup.id}}">{{eventData.eventGroup.name}}</a>
- </li>
- {{/if}}
- {{#if eventData.url}}
- <li>
- <span class="fa-li">
- <i class="fas fa-fw fa-link"></i>
- </span>
- <a href="{{eventData.url}}">
- {{eventData.url}}
- </a>
- </li>
- {{/if}}
- <li>
- <span class="fa-li">
- <i class="fas fa-fw fa-share-square"></i>
- </span>
- <a href="https://gath.io/{{eventData.id}}">gath.io/{{eventData.id}}</a>
- <button type="button" id="copyEventLink" class="eventInformationAction btn btn-outline-secondary btn-sm" data-clipboard-text="https://gath.io/{{eventData.id}}">
- <i class="fas fa-copy"></i> Copy
- </button>
- </li>
- </ul>
+ </aside>
+ </div>
</div>
</div>
+
{{#if eventHasConcluded}}
<div class="alert alert-warning mb-4" role="alert">
This event has concluded. It can no longer be edited, and will be automatically deleted <span class="daysToDeletion"></span>.
@@ -375,6 +385,29 @@
})
$(document).ready(function() {
+ // From https://davidwalsh.name/javascript-download
+ function downloadFile(data, fileName, type="text/plain") {
+ // Create an invisible A element
+ const a = document.createElement("a");
+ a.style.display = "none";
+ document.body.appendChild(a);
+
+ // Set the HREF to a Blob representation of the data to be downloaded
+ a.href = window.URL.createObjectURL(
+ new Blob([data], { type })
+ );
+
+ // Use download attribute to set set desired file name
+ a.setAttribute("download", fileName);
+
+ // Trigger the download by simulating click
+ a.click();
+
+ // Cleanup
+ window.URL.revokeObjectURL(a.href);
+ document.body.removeChild(a);
+ }
+
$.uploadPreview({
input_field: "#image-upload",
preview_box: "#image-preview",
@@ -394,6 +427,12 @@
{{/if}}
new ClipboardJS('#copyEventLink');
autosize($('textarea'));
+ $("#exportICS").click(function(){
+ let eventID = $(this).attr('data-event-id');
+ $.get('/exportevent/' + eventID, function(response) {
+ downloadFile(response, eventID + '.ics');
+ })
+ })
$("#copyEventLink").click(function(){
$(this).html('<i class="fas fa-copy"></i> Copied!');
setTimeout(function(){ $("#copyEventLink").html('<i class="fas fa-copy"></i> Copy');}, 5000);