diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 602c94624..666145e84 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -880,5 +880,6 @@ "search-contract": "๐Ÿ“„ {{searchTerm}}", "search-summon": "๐Ÿ”ฅ Summon of {{dao}}", "search-ragequit": "๐Ÿ’ธ Ragequit of {{shares}} shares from {{dao}} by {{address}}", - "search-default": "๐Ÿ” {{searchTerm}}" + "search-default": "๐Ÿ” {{searchTerm}}", + "search-dates": "๐Ÿ“… {{searchTerm}}" } diff --git a/imports/api/server/methods.js b/imports/api/server/methods.js index 31e905160..d6722d20c 100644 --- a/imports/api/server/methods.js +++ b/imports/api/server/methods.js @@ -246,9 +246,23 @@ Meteor.methods({ * @summary given a keyword returns contract id * @param {keyword} keyword identify contract by given keyword */ + getProposalContract(contractId) { + check(contractId, String); + log(`{ method: 'getProposalContract', user: ${logUser()}, _id: '${contractId}' }`); + const contract = Contracts.findOne({ _id: contractId }); + if (contract.pollId) { + return Contracts.findOne({ _id: contract.pollId }); + } + return contract; + }, + + /** + * @summary given a keyword returns contract id + * @param {keyword} keyword identify contract by given keyword + */ getContractById(contractId) { check(contractId, String); - log(`{ method: 'getContractById', user: ${logUser()}, _id: '${contractId}' }`); + log(`{ method: 'getProposalContract', user: ${logUser()}, _id: '${contractId}' }`); return Contracts.findOne({ _id: contractId }); }, diff --git a/imports/api/server/publications.js b/imports/api/server/publications.js index 67dd6801e..fe94e3feb 100644 --- a/imports/api/server/publications.js +++ b/imports/api/server/publications.js @@ -50,6 +50,20 @@ Meteor.publish('singleDao', function (daoQuery) { return this.ready(); }); +/** +* @summary gets information of proposals from a given date +* @return {Object} user data +*/ +Meteor.publish('dateContracts', function (terms) { + check(terms, Object); + const parameters = query(terms); + const contracts = Contracts.find(parameters.find, parameters.options); + if (contracts.count() > 0) { + return contracts; + } + return this.ready(); +}); + /** * @summary transactions between a user and a contract * @return {Object} querying terms diff --git a/imports/startup/both/routes.js b/imports/startup/both/routes.js index 3db91f9e6..22e22f372 100644 --- a/imports/startup/both/routes.js +++ b/imports/startup/both/routes.js @@ -260,6 +260,35 @@ Router.route('/dao/:dao', { }); +/** +* @summary loads a post using date in url +**/ +Router.route('/date', { + name: 'date', + template: 'home', + loadingTemplate: 'load', + onBeforeAction() { + Session.set('sidebarMenuSelectedId', 999); + _reset(); + this.next(); + }, + waitOn() { + return Meteor.subscribe('dateContracts', { view: 'dateRange', from: this.params.query.from, until: this.params.query.until }); + }, + data() { + if (this.ready()) { + return { + options: { view: 'dateRange', from: this.params.query.from, until: this.params.query.until }, + }; + } + return {}; + }, + onAfterAction() { + _boilerPlate(); + }, +}); + + /** * @summary loads a post using date in url **/ diff --git a/imports/ui/modules/chronos.js b/imports/ui/modules/chronos.js index b5a34895d..56942d160 100644 --- a/imports/ui/modules/chronos.js +++ b/imports/ui/modules/chronos.js @@ -37,11 +37,20 @@ const _timeCompressed = (date, micro) => { return buildSentence(seconds, 'compressed', micro); }; -const _timeComplete = (date) => { +const _timeDateOnly = (date) => { const options = { year: 'numeric', month: 'long', day: 'numeric' }; - return `${_timeAgo(date)} · ${date.toLocaleDateString('en', options)} · ${(date.getHours() < 10) ? `0${(date.getHours())}` : date.getHours()}:${(date.getMinutes() < 10) ? `0${(date.getMinutes())}` : date.getMinutes()}`; + return `${date.toLocaleDateString('en', options)}`; +}; + +const _hourOnly = (date) => { + return `${(date.getHours() < 10) ? `0${(date.getHours())}` : date.getHours()}:${(date.getMinutes() < 10) ? `0${(date.getMinutes())}` : date.getMinutes()}`; }; +const _timeComplete = (date) => { + return `${_timeAgo(date)} · ${_timeDateOnly(date)} · ${_hourOnly(date)}`; +}; + + const timeLeft = (date) => { const seconds = Math.floor((date - new Date()) / 1000); if (seconds > 0) { @@ -50,6 +59,8 @@ const timeLeft = (date) => { return false; }; +export const hourOnly = _hourOnly; +export const timeDateOnly = _timeDateOnly; export const timeCompressed = _timeCompressed; export const timeSince = _timeAgo; export const timeComplete = _timeComplete; diff --git a/imports/ui/templates/components/decision/ledger/ledger.js b/imports/ui/templates/components/decision/ledger/ledger.js index 9f283f7f2..1f1c22e2c 100644 --- a/imports/ui/templates/components/decision/ledger/ledger.js +++ b/imports/ui/templates/components/decision/ledger/ledger.js @@ -38,6 +38,9 @@ const _convertQuery = (instance) => { case 'search': tally.options.view = 'transactionsSearch'; break; + case 'dateRange': + tally.options.view = 'transactionsDate'; + break; default: } return tally; diff --git a/imports/ui/templates/widgets/feed/feed.js b/imports/ui/templates/widgets/feed/feed.js index 4f59787fa..641704572 100644 --- a/imports/ui/templates/widgets/feed/feed.js +++ b/imports/ui/templates/widgets/feed/feed.js @@ -100,6 +100,7 @@ const _isIndexFeed = (instance) => { || instance.options.view === 'token' || instance.options.view === 'transactionsToken' || instance.options.view === 'transactionsPeer' + || instance.options.view === 'transactionsDate' || instance.options.view === 'transactionsGeo' || instance.options.view === 'peer' || instance.options.view === 'threadVotes' diff --git a/imports/ui/templates/widgets/feed/feedItem.html b/imports/ui/templates/widgets/feed/feedItem.html index cbea2e6f4..d68a5d675 100644 --- a/imports/ui/templates/widgets/feed/feedItem.html +++ b/imports/ui/templates/widgets/feed/feedItem.html @@ -86,7 +86,7 @@ {{#if summon}} {{> warning label="moloch-summon-dao" style='summon'}}
-
{{{sinceDate createdAt}}}
+ {{{sinceDate createdAt}}}
@@ -116,7 +116,7 @@ {{#if ragequit}} {{> warning label="moloch-contract-ragequit" style='summon ragequit'}}
-
{{{sinceDate createdAt}}}
+ {{{sinceDate createdAt}}}
@@ -135,7 +135,7 @@ {{else}} {{{description}}}
-
{{{sinceDate createdAt}}}
+ {{{sinceDate createdAt}}}
diff --git a/imports/ui/templates/widgets/feed/feedItem.js b/imports/ui/templates/widgets/feed/feedItem.js index c9dd86068..248b07d96 100644 --- a/imports/ui/templates/widgets/feed/feedItem.js +++ b/imports/ui/templates/widgets/feed/feedItem.js @@ -6,9 +6,9 @@ import { $ } from 'meteor/jquery'; import { Router } from 'meteor/iron:router'; import { ReactiveVar } from 'meteor/reactive-var'; -import { getProfileFromUsername, getAnonymous } from '/imports/startup/both/modules/User'; +import { getAnonymous } from '/imports/startup/both/modules/User'; import { removeContract } from '/imports/startup/both/modules/Contract'; -import { getProfileName, stripHTMLfromText } from '/imports/ui/modules/utils'; +import { stripHTMLfromText } from '/imports/ui/modules/utils'; import { timeComplete } from '/imports/ui/modules/chronos'; import { displayModal } from '/imports/ui/modules/modal'; import { animationSettings } from '/imports/ui/modules/animation'; @@ -19,9 +19,9 @@ import { templetize, getImage } from '/imports/ui/templates/layout/templater'; import { tokenWeb } from '/lib/token'; import { wrapURLs } from '/lib/utils'; import { Collectives } from '/imports/api/collectives/Collectives'; +import { createDateQuery } from '/imports/ui/templates/widgets/transaction/transaction'; import '/imports/ui/templates/widgets/feed/feedItem.html'; -import '/imports/ui/templates/widgets/transaction/transaction.js'; import '/imports/ui/templates/widgets/spinner/spinner.js'; import '/imports/ui/templates/components/identity/avatar/avatar.js'; import '/imports/ui/templates/components/decision/countdown/countdown.js'; @@ -695,6 +695,13 @@ Template.feedItem.helpers({ style: this.period ? `period period-${this.period.toLowerCase()}` : '', }; }, + dateURL() { + const from = this.timestamp; + const fromQuery = createDateQuery(from); + const until = new Date(this.timestamp.getTime() + (60 * 60 * 24 * 1000)); + const untilQuery = createDateQuery(until); + return `/date?from=${fromQuery}&until=${untilQuery}`; + }, }); Template.feedItem.events({ diff --git a/imports/ui/templates/widgets/preview/preview.js b/imports/ui/templates/widgets/preview/preview.js index 413359473..38ebfd91b 100644 --- a/imports/ui/templates/widgets/preview/preview.js +++ b/imports/ui/templates/widgets/preview/preview.js @@ -6,6 +6,7 @@ import { $ } from 'meteor/jquery'; import { stripHTMLfromText } from '/imports/ui/modules/utils'; import { Contracts } from '/imports/api/contracts/Contracts'; import { templetize, getImage } from '/imports/ui/templates/layout/templater'; +import { getProposalDescription } from '/imports/ui/templates/widgets/feed/feedItem'; import '/imports/ui/templates/widgets/preview/preview.html'; @@ -23,20 +24,40 @@ const _getCharLength = (id) => { Template.preview.onCreated(function () { Template.instance().feed = new ReactiveVar(); Template.instance().contract = new ReactiveVar(); + Template.instance().proposal = new ReactiveVar(); const instance = this; const _id = Template.currentData().contractId; if (_id) { const contract = Contracts.findOne({ _id }); - if (!contract) { + if (contract && contract.pollId) { + instance.contract.set(contract); + Meteor.call('getProposalContract', Template.currentData().contractId, function (error, result) { + if (result) { + instance.proposal.set(result); + } else if (error) { + console.log(error); + } + }); + } else if (!contract) { Meteor.call('getContractById', Template.currentData().contractId, function (error, result) { if (result) { instance.contract.set(result); + + if (result.pollId) { + Meteor.call('getProposalContract', result._id, function (err, res) { + if (res) { + instance.proposal.set(res); + } else if (err) { + console.log(err); + } + }); + } } else if (error) { console.log(error); } }); - } else { + } else if (contract) { instance.contract.set(contract); } } @@ -69,6 +90,10 @@ Template.preview.helpers({ return title; }, fullTitle() { + const proposal = Template.instance().proposal.get(); + if (proposal && proposal.title) { + return `${getProposalDescription(proposal.title, true).substring(0, 45)}...`; + } return stripHTMLfromText(Template.instance().contract.get().title); }, url() { diff --git a/imports/ui/templates/widgets/search/search.jsx b/imports/ui/templates/widgets/search/search.jsx index 763ab5abd..5b6d9262b 100644 --- a/imports/ui/templates/widgets/search/search.jsx +++ b/imports/ui/templates/widgets/search/search.jsx @@ -6,6 +6,7 @@ import { TAPi18n } from 'meteor/tap:i18n'; import { Contracts } from '/imports/api/contracts/Contracts'; import { Collectives } from '/imports/api/collectives/Collectives'; import { getProposalDescription } from '/imports/ui/templates/widgets/feed/feedItem'; +import { timeDateOnly } from '/imports/ui/modules/chronos'; import web3 from 'web3'; @@ -64,6 +65,13 @@ const _setTags = () => { text: TAPi18n.__('search-contract').replace('{{searchTerm}}', _dynamicTitle(getProposalDescription(contract.title, true))), }, ]; + } else if (params.query.from) { + query = [ + { + id: Router.current().params.query.from, + text: TAPi18n.__('search-dates').replace('{{searchTerm}}', timeDateOnly(new Date(Router.current().params.query.from))), + }, + ]; } } diff --git a/imports/ui/templates/widgets/tally/tally.js b/imports/ui/templates/widgets/tally/tally.js index 8768e208b..ed355fc05 100644 --- a/imports/ui/templates/widgets/tally/tally.js +++ b/imports/ui/templates/widgets/tally/tally.js @@ -122,6 +122,7 @@ const _defaultTally = (view) => { view === 'periodVotes' || view === 'transactionsToken' || view === 'transactionsPeer' || + view === 'transactionsDate' || view === 'transactionsGeo' || view === 'transactionsDao' || view === 'transactionsSearch' diff --git a/imports/ui/templates/widgets/transaction/transaction.html b/imports/ui/templates/widgets/transaction/transaction.html index 338a11add..35e0b1774 100644 --- a/imports/ui/templates/widgets/transaction/transaction.html +++ b/imports/ui/templates/widgets/transaction/transaction.html @@ -160,7 +160,7 @@
{{_ 'pending'}}
{{else}} -
{{sinceDate}}
+ {{sinceDate}} {{/if}}
diff --git a/imports/ui/templates/widgets/transaction/transaction.js b/imports/ui/templates/widgets/transaction/transaction.js index 796e93b43..8cf522daa 100644 --- a/imports/ui/templates/widgets/transaction/transaction.js +++ b/imports/ui/templates/widgets/transaction/transaction.js @@ -5,7 +5,7 @@ import { Session } from 'meteor/session'; import { Meteor } from 'meteor/meteor'; import { getVotes } from '/imports/api/transactions/transaction'; -import { timeCompressed } from '/imports/ui/modules/chronos'; +import { timeCompressed, timeDateOnly, hourOnly } from '/imports/ui/modules/chronos'; import { token } from '/lib/token'; import { Transactions } from '/imports/api/transactions/Transactions'; import { syncBlockchain } from '/imports/startup/both/modules/metamask'; @@ -91,6 +91,15 @@ const _getContractToken = (transaction) => { return coin; }; + +/** +* @summary creates string of date for URL +* @return {string} uri +*/ +const _createDateQuery = (date) => { + return `${date.getFullYear()}-${(date.getMonth() < 9) ? `0${parseInt(date.getMonth() + 1, 10)}` : parseInt(date.getMonth() + 1, 10)}-${(date.getDate() < 10) ? `0${date.getDate()}` : date.getDate()}`; +}; + Template.transaction.onCreated(function () { Template.instance().totalVotes = new ReactiveVar(0); Template.instance().loading = new ReactiveVar(false); @@ -216,6 +225,16 @@ Template.transaction.helpers({ sinceDate() { return `${timeCompressed(this.contract.timestamp, true)}`; }, + dateLink() { + const from = this.contract.timestamp; + const fromQuery = _createDateQuery(from); + const until = new Date(this.contract.timestamp.getTime() + (60 * 60 * 24 * 1000)); + const untilQuery = _createDateQuery(until); + return `/date?from=${fromQuery}&until=${untilQuery}`; + }, + dateDescription() { + return `${timeDateOnly(this.contract.timestamp)} ยท ${hourOnly(this.contract.timestamp)}`; + }, ragequit() { return this.isRagequit; }, @@ -321,9 +340,9 @@ Template.collectivePreview.helpers({ return Template.instance().collective.name; }, url() { - // console.log(Template.instance().collective); return `/dao/${Template.instance().collective.uri}`; }, }); export const getContractToken = _getContractToken; +export const createDateQuery = _createDateQuery; diff --git a/lib/views.js b/lib/views.js index d110e0004..7ee89d36d 100644 --- a/lib/views.js +++ b/lib/views.js @@ -834,6 +834,48 @@ _views.contractVotes = (terms) => { }; }; +/** +* @summary gets contracts that belong to a given range of dates +* @param {object} terms filters and limits +* @return {object} query to use on collection +*/ +_views.dateRange = (terms) => { + log(`{ view: 'dateRange', user: ${logUser()}, from: '${terms.from}', until: '${terms.until}' }`); + const from = new Date(`${terms.from}T00:00:00`); + const until = new Date(`${terms.until}T00:00:00`); + return { + find: { timestamp: { $gte: from, $lt: until }, stage: { $ne: 'DRAFT' }, kind: { $ne: 'DELEGATION' }, pollId: { $exists: false }, replyId: { $exists: false } }, + options: { sort: { timestamp: -1 }, limit: terms.limit, skip: terms.skip }, + }; +}; + + +/** +* @summary returns transactions based on a range of dates +* @param {object} terms filters and limits +* @return {object} query to use on collection +*/ +_views.transactionsDate = (terms) => { + log(`{ view: 'transactionsDate', user: ${logUser()}, from: '${terms.from}', until: '${terms.until}' }`); + const from = new Date(`${terms.from}T00:00:00`); + const until = new Date(`${terms.until}T00:00:00`); + const contracts = Contracts.find({ timestamp: { $gte: from, $lt: until }, stage: { $ne: 'DRAFT' }, kind: { $ne: 'DELEGATION' }, pollId: { $exists: false }, replyId: { $exists: false }, period: { $ne: 'RAGEQUIT' } }).fetch(); + if (contracts.length > 0) { + const contractIds = _.uniq(_.pluck(contracts, '_id')); + const query = []; + for (const id of contractIds) { + query.push({ + contractId: id, + }); + } + return { + find: { $or: query }, + }; + } + return {}; +}; + + /** * @summary all the daos listed in this server * @param {object} terms filters and limits