├── .npmignore ├── .gitignore ├── .eslintrc.js ├── example ├── locales │ ├── en.js │ └── de.js ├── server.js └── client.js ├── Makefile ├── LICENSE ├── package.json ├── index.js ├── spec.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | example 2 | .jshintrc 3 | .npmignore 4 | LICENSE 5 | Makefile 6 | spec.js 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "browser": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended" 9 | ], 10 | "rules": { 11 | "indent": ["error", 2], 12 | "linebreak-style": ["error", "unix"], 13 | "quotes": ["error", "single"], 14 | "semi": ["error", "always"] 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /example/locales/en.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | example: { 3 | this_page_was_requested: 'This page was requested %(ago)s.', 4 | auto_update_hint: 'In order to see the auto-update, please let some time pass by.', 5 | tooltip_hint: 'Hovering the mouse pointer over the bold text displays a tooltip with the actual date/time.', 6 | switch_locale: 'Switch Locale: ' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /example/locales/de.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | example: { 3 | this_page_was_requested: 'Diese Seite wurde %(ago)s abgerufen.', 4 | auto_update_hint: 'Um das Auto-Update zu sehen, lass bitte ein wenig Zeit verstreichen.', 5 | tooltip_hint: 'Fährt man mit dem Mauszeiger über den fetten Text, wird der eigentliche Zeitstempel in einem Tooltip angezeigt.', 6 | switch_locale: 'Locale ändern: ' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = ./node_modules/.bin 2 | 3 | test: lint 4 | @$(BIN)/mocha -t 5000 -b -R spec spec.js 5 | 6 | lint: 7 | @$(BIN)/eslint index.js example/ 8 | 9 | install: 10 | npm install 11 | 12 | example:: 13 | @$(BIN)/node-dev example/server.js 14 | 15 | release-patch: test 16 | @$(call release,patch) 17 | 18 | release-minor: test 19 | @$(call release,minor) 20 | 21 | release-major: test 22 | @$(call release,major) 23 | 24 | publish: 25 | git push --tags origin HEAD:master 26 | npm publish 27 | 28 | define release 29 | npm version $(1) --message 'release v%s' 30 | endef 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Martin Andert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var browserify = require('connect-browserify'); 5 | var reactify = require('reactify'); 6 | var React = require('react'); 7 | var ReactDOM = require('react-dom/server'); 8 | 9 | require('node-jsx').install(); 10 | 11 | var App = React.createFactory(require('./client')); 12 | 13 | express() 14 | .use('/bundle.js', browserify.serve({ 15 | entry: __dirname + '/client', 16 | debug: true, watch: true, 17 | transforms: [reactify] 18 | })) 19 | .get('/style.css', function(req, res) { 20 | res.setHeader('Content-Type', 'text/css'); 21 | res.send('body { margin-top: 100px; text-align: center; } h1 { font-weight: normal; margin: 0 20px 20px; } time { font-weight: bold; } p { margin: 0 20px 5px; }'); 22 | }) 23 | .get('/', function(req, res) { 24 | var now = Date.now(); 25 | 26 | res.cookie('serverTime', now, { maxAge: 10000, httpOnly: false }); 27 | res.send(ReactDOM.renderToString(App({ serverTime: now }))); 28 | }) 29 | .listen(3000, function() { 30 | // eslint-disable-next-line no-console 31 | console.log('Point your browser to http://localhost:3000'); 32 | }); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ago-component", 3 | "version": "0.8.1", 4 | "description": "A multi-lingual component for React that renders the approximate time ago in words from a specific past date using an HTML5 time element", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/martinandert/react-ago-component.git" 9 | }, 10 | "keywords": [ 11 | "react", 12 | "react-component", 13 | "timeago", 14 | "time-ago", 15 | "date", 16 | "time", 17 | "elapsed" 18 | ], 19 | "author": { 20 | "name": "Martin Andert", 21 | "email": "mandert@gmail.com" 22 | }, 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/martinandert/react-ago-component/issues" 26 | }, 27 | "homepage": "https://github.com/martinandert/react-ago-component", 28 | "dependencies": { 29 | "create-react-class": "^15.6.2", 30 | "damals": "^0.9.2", 31 | "object-assign": "^4.1.0", 32 | "prop-types": "^15.6.0", 33 | "react-dom-factories": "^1.0.2" 34 | }, 35 | "peerDependencies": { 36 | "counterpart": ">=0.18.4" 37 | }, 38 | "devDependencies": { 39 | "connect-browserify": "^4.0.0", 40 | "counterpart": ">=0.18.4", 41 | "eslint": "^3.19.0", 42 | "eslint-plugin-react": "^7.0.1", 43 | "express": "^4.10.5", 44 | "mocha": "^3.4.1", 45 | "node-dev": "^3.1.3", 46 | "node-jsx": "^0.13.3", 47 | "react": "^16.0.0", 48 | "react-dom": "^16.0.0", 49 | "react-interpolate-component": "^0.11.0", 50 | "react-translate-component": "^0.14.0", 51 | "reactify": "^1.1.0", 52 | "time": "^0.12.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PropTypes = require('prop-types'); 4 | var createReactClass = require('create-react-class'); 5 | var timeAgo = require('damals'); 6 | var counterpart = require('counterpart'); 7 | var strftime = require('counterpart/strftime'); 8 | var assign = require('object-assign'); 9 | var DOM = require('react-dom-factories'); 10 | 11 | var toString = Object.prototype.toString; 12 | 13 | function isString(value) { 14 | return toString.call(value) === '[object String]'; 15 | } 16 | 17 | function isNumber(value) { 18 | return toString.call(value) === '[object Number]'; 19 | } 20 | 21 | var Ago = createReactClass({ 22 | displayName: 'Ago', 23 | ticker: null, 24 | 25 | propTypes: { 26 | date: PropTypes.oneOfType([ 27 | PropTypes.instanceOf(Date), 28 | PropTypes.string, 29 | PropTypes.number 30 | ]), 31 | tooltipFormat: PropTypes.string, 32 | autoUpdate: PropTypes.oneOfType([ 33 | PropTypes.bool, 34 | PropTypes.number 35 | ]) 36 | }, 37 | 38 | getDefaultProps: function() { 39 | return { 40 | date: new Date(), 41 | tooltipFormat: 'default', 42 | autoUpdate: 0 43 | }; 44 | }, 45 | 46 | componentDidMount: function() { 47 | if (this.props.autoUpdate) { 48 | var delay = isNumber(this.props.autoUpdate) ? this.props.autoUpdate * 1000 : 2600; 49 | 50 | this.ticker = setInterval(this.invalidate, delay); 51 | } 52 | 53 | counterpart.onLocaleChange(this.invalidate); 54 | }, 55 | 56 | componentWillUnmount: function() { 57 | if (this.ticker) { 58 | clearInterval(this.ticker); 59 | this.ticker = null; 60 | } 61 | 62 | counterpart.offLocaleChange(this.invalidate); 63 | }, 64 | 65 | invalidate: function() { 66 | this.forceUpdate(); 67 | }, 68 | 69 | render: function() { 70 | var date = this.props.date; 71 | 72 | if (isString(date) || isNumber(date)) { 73 | date = new Date(date); 74 | } 75 | 76 | var content = timeAgo(date); 77 | var dateTime = strftime(date, '%Y-%m-%dT%H:%M:%S%z'); 78 | var title = counterpart.localize(date, { format: this.props.tooltipFormat }); 79 | 80 | var props = assign({}, this.props, { dateTime: dateTime, title: title }); 81 | delete props.date; 82 | delete props.tooltipFormat; 83 | delete props.autoUpdate; 84 | 85 | return DOM.time(props, content); 86 | } 87 | }); 88 | 89 | module.exports = Ago; 90 | -------------------------------------------------------------------------------- /example/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var createReactClass = require('create-react-class'); 5 | var PropTypes = require('prop-types'); 6 | var ReactDOM = require('react-dom'); 7 | var counterpart = require('counterpart'); 8 | var Translate = require('react-translate-component'); 9 | var Ago = require('../'); 10 | 11 | // on-demand library translations 12 | counterpart.registerTranslations('de', require('counterpart/locales/de')); 13 | counterpart.registerTranslations('de', require('damals/locales/de')); 14 | 15 | // our app's translations 16 | counterpart.registerTranslations('en', require('./locales/en')); 17 | counterpart.registerTranslations('de', require('./locales/de')); 18 | 19 | var LocaleSwitcher = createReactClass({ 20 | handleChange: function(e) { 21 | counterpart.setLocale(e.target.value); 22 | }, 23 | 24 | render: function() { 25 | return ( 26 |
27 |