diff --git a/.meteor/packages b/.meteor/packages index befa8a2..ee82606 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -51,3 +51,12 @@ percolate:migrations # db migrations ostrio:meteor-root meteortesting:mocha practicalmeteor:chai +aldeed:schema-deny +aldeed:template-extension +dburles:collection-helpers +less +markdown +mongo-livedata +reywood:publish-composite +barbatus:stars-rating +arkham:comments-ui diff --git a/.meteor/versions b/.meteor/versions index 8a3258f..8a03c20 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -7,12 +7,16 @@ accounts-oauth@1.1.15 accounts-password@1.5.0 alanning:roles@1.2.16 aldeed:collection2-core@2.0.1 +aldeed:schema-deny@2.0.1 +aldeed:template-extension@4.1.0 alexwine:bootstrap-4@4.0.0-beta.2 allow-deny@1.1.0 +arkham:comments-ui@1.4.4 audit-argument-checks@1.0.7 autoupdate@1.3.12 babel-compiler@6.24.7 babel-runtime@1.1.1 +barbatus:stars-rating@1.1.1 base64@1.0.10 binary-heap@1.0.10 blaze@2.3.2 @@ -23,6 +27,7 @@ caching-html-compiler@1.1.2 callback-hook@1.0.10 check@1.2.5 coffeescript@1.0.17 +dburles:collection-helpers@1.1.0 dburles:mongo-collection-instances@0.3.5 ddp@1.4.0 ddp-client@2.2.0 @@ -55,9 +60,11 @@ id-map@1.0.9 jquery@1.11.10 lai:collection-extensions@0.2.1_1 launch-screen@1.1.1 +less@2.7.11 livedata@1.0.18 localstorage@1.2.0 logging@1.1.19 +markdown@1.0.12 maximum:package-base@1.1.2 maximum:server-transform@0.5.0 mdg:geolocation@1.3.0 @@ -77,6 +84,7 @@ modules-runtime@0.9.0 mongo@1.3.0 mongo-dev-server@1.1.0 mongo-id@1.0.6 +mongo-livedata@1.0.12 mys:fonts@0.0.2 natestrauser:publish-performant-counts@0.1.2 npm-bcrypt@0.9.3 @@ -104,6 +112,7 @@ reactive-dict@1.2.0 reactive-var@1.0.11 reload@1.1.11 retry@1.0.9 +reywood:publish-composite@1.5.2 routepolicy@1.0.12 selaias:cookie-consent@0.4.0 service-configuration@1.0.11 diff --git a/imports/api/Common/FiresSchema.js b/imports/api/Common/FiresSchema.js index cdaae88..5bb792b 100644 --- a/imports/api/Common/FiresSchema.js +++ b/imports/api/Common/FiresSchema.js @@ -7,6 +7,7 @@ const firesCommonSchema = { ourid: LocationSchema, lat: Number, lon: Number, + address: { type: String, optional: true }, scan: Number, type: String, when: Date, diff --git a/imports/api/Fires/server/publications.js b/imports/api/Fires/server/publications.js index 7e9be4d..6737510 100644 --- a/imports/api/Fires/server/publications.js +++ b/imports/api/Fires/server/publications.js @@ -6,23 +6,43 @@ import { check } from 'meteor/check'; import urlEnc from '/imports/modules/url-encode'; import { Promise } from 'meteor/promise'; import FiresCollection from '../Fires'; +import NodeGeocoder from 'node-geocoder'; +import { gmapServerKey } from '/imports/startup/server/IPGeocoder'; function findFire(unsealed) { const fire = FiresCollection.find({ ourid: { type: 'Point', coordinates: [unsealed.lon, unsealed.lat] } }); return fire; } +const options = { + provider: 'google', + // Optional depending on the providers + httpAdapter: 'https', // Default + apiKey: gmapServerKey, // for Mapquest, OpenCage, Google Premier + formatter: null // 'gpx', 'string', ... +}; + +const geocoder = NodeGeocoder(options); + Meteor.publish('fireFromHash', function fireFromHash(fireEnc) { check(fireEnc, String); try { // console.log(fireEnc); const unsealed = Promise.await(urlEnc.decrypt(fireEnc)); const w = unsealed.when; - // console.log(w); unsealed.when = new Date(w); // console.log(unsealed); FiresCollection.schema.validate(unsealed); const fire = findFire(unsealed); + try { + const rev = Promise.await(geocoder.reverse({ lat: unsealed.lat, lon: unsealed.lon })); + if (rev[0]) { + unsealed.address = rev[0].formattedAddress; + } + // console.log(unsealed.address); + } catch (reve) { + console.error(reve); + } if (fire.count() === 0) { const result = FiresCollection.upsert({ ourid: unsealed.ourid }, { $set: unsealed }, { multi: false, upsert: true }); console.log(JSON.stringify(result)); diff --git a/imports/startup/client/comments.js b/imports/startup/client/comments.js new file mode 100644 index 0000000..e4c1575 --- /dev/null +++ b/imports/startup/client/comments.js @@ -0,0 +1,42 @@ +/* global Comments */ +/* eslint-disable import/no-absolute-path */ + +import i18n from '/imports/startup/client/i18n'; +import '/imports/startup/common/comments'; + +import './comments.scss'; + +i18n.init((err, t) => { + Comments.ui.setContent({ + title: ' ', // i18n.t('Comentarios'), + save: t('Guardar'), + reply: t('Responder'), + edit: t('Editar'), + remove: t('Borrar'), + 'placeholder-textarea': t('Añadir un comentario'), + 'add-button-reply': t('Añadir una respuesta'), + 'add-button': t('Añadir comentario'), + 'you-need-to-login': t('Necesitas iniciar sesión para'), + 'add comments': t('añadir comentarios'), + 'like comments': t('puntuar comentarios'), + 'rate comments': t('puntuar comentarios'), + 'add replies': t('responder'), + 'load-more': t('Más comentarios') + }); + + /* This in client side */ + Comments.ui.config({ + limit: 20, // default 10 + loadMoreCount: 20, // default 20 + generateAvatar: function genAvatar(user, isAnonymous) { + if (isAnonymous) { + return i18n.t('Anónimo'); + } + return user.profile && user.profile.name && user.profile.name.first ? user.profile.name.first : null; + }, + template: 'bootstrap', // default 'semantic-ui' + // default 'http://s3.amazonaws.com/37assets/svn/765-default-avatar.png' + defaultAvatar: '/default-avatar.png', + markdown: false + }); +}); diff --git a/imports/startup/client/comments.scss b/imports/startup/client/comments.scss new file mode 100644 index 0000000..de93e65 --- /dev/null +++ b/imports/startup/client/comments.scss @@ -0,0 +1,29 @@ +.comments-section { + width: 100%; +} + +.comments-box { + max-width: inherit; + padding: 0; +} + +p.comment-content { + /* white-space: pre-line; */ +} + +/* remove comment btn danger style */ + +div.media-body.comment> div> div> div.btn.btn-danger.remove-action { + background-color: white; + border: 1px solid rgb(204, 204, 204); + color: rgb(51, 51, 51); +} + +/* text-area height */ +.form-control .create-comment { + height: 9em; +} + +.img-avatar { + margin-right: 5px; +} \ No newline at end of file diff --git a/imports/startup/common/comments.js b/imports/startup/common/comments.js new file mode 100644 index 0000000..379f836 --- /dev/null +++ b/imports/startup/common/comments.js @@ -0,0 +1,16 @@ +/* global Comments */ + +// Client and Server +Comments.config({ + rating: 'likes-and-dislikes', + allowAnonymous: () => true, + anonymousSalt: 'klasddl3lala0l3lasdlas0ol3lasdlao3lasdoaslaldal3lasdclasdlal3lasdladlaq', + publishUserFields: { + profile: 1 + }, + generateUsername: function genUser(user) { + console.log(JSON.stringify(user)); + // FIXME + return user.profile && user.profile.name && user.profile.name.first ? user.profile.name.first : ''; + } +}); diff --git a/imports/startup/server/IPGeocoder.js b/imports/startup/server/IPGeocoder.js index 70e5395..96c5ef1 100644 --- a/imports/startup/server/IPGeocoder.js +++ b/imports/startup/server/IPGeocoder.js @@ -54,6 +54,9 @@ export function localize() { return { location: { latitude: 40.4146500, longitude: -3.7004000 } }; } +export const gmapKey = process.env.GMAPS_KEY || Meteor.settings.gmaps.key; +export const gmapServerKey = Meteor.settings.gmaps.serverKey; + Meteor.methods({ geo: localize, getMapKey() { @@ -61,6 +64,6 @@ Meteor.methods({ // https://developers.google.com/maps/documentation/javascript/get-api-key // https://console.developers.google.com/ // export GMAPS_KEY=SomeGMapsKey - return process.env.GMAPS_KEY || Meteor.settings.gmaps.key; + return gmapKey; } }); diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js index 8dcf2fc..b6b01fe 100644 --- a/imports/startup/server/index.js +++ b/imports/startup/server/index.js @@ -6,3 +6,4 @@ import './email'; import './IPGeocoder'; import './migrations'; import './notificationsObserver'; +import '../common/comments'; diff --git a/imports/ui/pages/Fires/Fires.js b/imports/ui/pages/Fires/Fires.js index 8aeac94..3165f9f 100644 --- a/imports/ui/pages/Fires/Fires.js +++ b/imports/ui/pages/Fires/Fires.js @@ -6,31 +6,22 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { withTracker } from 'meteor/react-meteor-data'; import { translate } from 'react-i18next'; -import { Col, Row } from 'react-bootstrap'; import { Meteor } from 'meteor/meteor'; -import { Map, CircleMarker, Circle } from 'react-leaflet'; +import { Map, Circle } from 'react-leaflet'; +import moment from 'moment-timezone'; +import Blaze from 'meteor/gadicc:blaze-react-component'; import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers'; -import moment from 'moment'; import FiresCollection from '/imports/api/Fires/Fires'; +import '/imports/startup/client/comments'; + +import './Fires.scss'; class Fire extends React.Component { constructor(props) { super(props); this.state = { }; - } - - /* componentDidUpdate() { - * const map = this.firemap.leafletElement; - * map.invalidateSize(); - * } */ - - handleLeafletLoad(map) { - console.log(map); - if (map) { - // map.leafletELement.invalidateSize(); - // map.invalidateSize(); - } + // console.log(moment.tz.guess()); } render() { @@ -40,50 +31,52 @@ class Fire extends React.Component { {!loading &&

- {t('Información sobre fuego detectado el día {{when}}', { when: moment(fire.when).format('LLLL') })} + {fire.address ? + t('Información adicional sobre fuego detectado en {{where}} el {{when}}', { where: fire.address, when: moment(fire.when).tz(moment.tz.guess()).format('LLLL (z)') }) : + t('Información adicional sobre fuego detectado el {{when}}', { when: moment.utc(fire.when).format('LLLL') })}

- - - {(fire.type === 'modis' || fire.type === 'virrs') && -

{t('Detectado por satélites de la NASA')} -

- } -
Comentarios
-

{t('Añade un comentario si tienes información adicional sobre este fuego (por ejemplo, si está aún activo cómo acceder a él, o si conoces el motivo por el que comenzó el fuego, o si quieres denunciar algún tipo de ilegalidad relacionada con el fuego)')} -

- - - { - this.fireMap = map; - this.handleLeafletLoad(map); - }} - animate - sleep={false} - center={[fire.lat, fire.lon]} - zoom={8} - > - - - - - - - -
+ + { + this.fireMap = map; + }} + animate + sleep={false} + center={[fire.lat, fire.lon]} + className="fire-leaflet-container" + zoom={8} + > + + + + + +

{t('Coordenadas:')} {fire.lat}, {fire.lon}

+ {(fire.type === 'modis' || fire.type === 'virrs') && +

{t('Fuego detectado por satélites de la NASA {{when}}', { when: moment(fire.when).fromNow() })}

+ } + {/* TODO: marcar tipo de fuego, industria, etc */} +

{t('Comentarios')}

+
+ {t('Puedes añadir un comentario si tienes información adicional sobre este fuego.')} + {' '} + {t('Por ejemplo:')} + +
+
+ +
} diff --git a/imports/ui/pages/Fires/Fires.scss b/imports/ui/pages/Fires/Fires.scss new file mode 100644 index 0000000..d48dfa2 --- /dev/null +++ b/imports/ui/pages/Fires/Fires.scss @@ -0,0 +1,29 @@ +.fire-leaflet-container { + min-height: 30vh; + min-width: 40vw; + width: 50%; + margin: 0 0 10px 10px !important; + display: flex; + float: right !important; +} + +.ViewFire > h4.page-header { + line-height: 1.5em; +} + +@media (max-width: 768px) { + .leaflet-container { + width: 100%; + } +} + + +.comments-info { + font-size: 14px; + color: #818182; + /* display: inline-flex; */ + /* background-color: #fefefe; + border-color: #fdfdfe; + padding: 10px; + border-radius: 3px; */ +} \ No newline at end of file diff --git a/imports/ui/pages/FiresMap/FiresMap.scss b/imports/ui/pages/FiresMap/FiresMap.scss index 992f3e3..c2cf233 100644 --- a/imports/ui/pages/FiresMap/FiresMap.scss +++ b/imports/ui/pages/FiresMap/FiresMap.scss @@ -36,3 +36,8 @@ .leaflet-control-layers-toggle { background-image: url(/images/layers.png); } + + +.glyphicon .glyphicon-thumbs-up { + +} diff --git a/package-lock.json b/package-lock.json index 2266327..d22c36f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2120,6 +2120,11 @@ "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" } }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -8479,6 +8484,16 @@ "is-stream": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" } }, + "node-geocoder": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/node-geocoder/-/node-geocoder-3.21.1.tgz", + "integrity": "sha512-eRtx8X+TYT/z2Qe9W1vSbEhJk9c5pFPyzTXBB51/4TB1A0p4GI/e2zGPgNIoK6PN/uQz3JPFfjkq9Qdwy30eeA==", + "requires": { + "bluebird": "3.5.1", + "request": "2.83.0", + "request-promise": "4.2.2" + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -10522,6 +10537,25 @@ } } }, + "request-promise": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", + "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", + "requires": { + "bluebird": "3.5.1", + "request-promise-core": "1.1.1", + "stealthy-require": "1.1.1", + "tough-cookie": "2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "requires": { + "lodash": "4.17.4" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -11098,6 +11132,11 @@ } } }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", diff --git a/package.json b/package.json index e233ae2..53e4fea 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "meteor-node-stubs": "^0.3.2", "moment": "^2.19.1", "moment-timezone": "^0.5.14", + "node-geocoder": "^3.21.1", "nodemailer": "^4.4.1", "popper.js": "^1.12.7", "prop-types": "^15.6.0", diff --git a/packages/meteor-comments-ui b/packages/meteor-comments-ui new file mode 160000 index 0000000..3346b6d --- /dev/null +++ b/packages/meteor-comments-ui @@ -0,0 +1 @@ +Subproject commit 3346b6db70c153b1dd66e6a16a0ad28f10ebf740 diff --git a/private/pages/credits.md b/private/pages/credits.md index 3e4e86b..3d461d9 100644 --- a/private/pages/credits.md +++ b/private/pages/credits.md @@ -7,3 +7,7 @@ We acknowledge the use of data and imagery from LANCE FIRMS operated by the NASA https://commons.wikimedia.org/wiki/File:California_Wildfires_October_23_2007.jpg Source: http://www.nasa.gov/vision/earth/lookingatearth/socal_wildfires_oct07.html + +## Icons ## + +Default avater by Mimooh (Own work) [CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons diff --git a/public/default-avatar.png b/public/default-avatar.png new file mode 100644 index 0000000..f12cdf6 Binary files /dev/null and b/public/default-avatar.png differ diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 43fc194..467d831 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -189,5 +189,18 @@ "fireDetected": "fuego detectado {{when}}", "fireDetectedAt": - "fuego detectado el {{when}}" + "fuego detectado el {{when}}", + "Comentarios": "Comentarios", + "Guardar": "Guardar", + "Responder": "Responder", + "Editar": "Editar", + "Borrar": "Borrar", + "Añadir un comentario": "Añadir un comentario", + "Añadir una respuesta": "Añadir una respuesta", + "Añadir comentario": "Añadir comentario", + "Necesitas iniciar sesión para" :"Necesitas iniciar sesión para", + "añadir comentarios": "añadir comentarios", + "puntuar comentarios": "puntuar comentarios", + "responder": "responder", + "Más comentarios": "Más comentarios" } diff --git a/test/encode.test.js b/test/encode.test.js index 4f29a2c..1815582 100644 --- a/test/encode.test.js +++ b/test/encode.test.js @@ -45,18 +45,27 @@ describe('url encoding', () => { }; const sealed = await urlEnc.encrypt(obj); - console.log(encodeURI(sealed)); + const sealed2 = await urlEnc.encrypt(obj); + // console.log(encodeURI(sealed)); const unsealed = await urlEnc.decrypt(sealed); chai.expect(unsealed).to.deep.equal(obj); chai.expect(sealed).to.equal(encodeURI(sealed)); + chai.expect(sealed).to.not.equal(sealed2); }); - // This fails because Date is return as String (not as Date) - /* it('should encrypt and dcrypt collection objects', async () => { + // This fails because Date is return as String (not as Date) and because _id + it('should encrypt and dcrypt collection objects', async () => { const obj = ActiveFiresCollection.findOne(); + delete obj._id; const sealed = await urlEnc.encrypt(obj); const unsealed = await urlEnc.decrypt(sealed); + const w = unsealed.when; + unsealed.when = new Date(w); + const c = unsealed.createdAt; + unsealed.createdAt = new Date(c); + const u = unsealed.updatedAt; + unsealed.updatedAt = new Date(u); chai.expect(unsealed).to.deep.equal(obj); - }); */ + }); });