UNPKG

ngx-timeago

Version:

Live updating timestamps in Angular 6+.

401 lines (391 loc) 17.1 kB
import * as i0 from '@angular/core'; import { Injectable, Directive, Optional, Input, Pipe, NgModule } from '@angular/core'; import { Subject, of, timer, empty } from 'rxjs'; import { expand, skip, filter } from 'rxjs/operators'; function isDefined(value) { return typeof value !== 'undefined' && value !== null; } function coerceBooleanProperty(value) { return value != null && `${value}` !== 'false'; } function dateParser(date) { const parsed = new Date(date); if (!Number.isNaN(parsed.valueOf())) { return parsed; } const parts = String(date).match(/\d+/g); if (parts === null || parts.length <= 2) { return parsed; } else { const [firstP, secondP, ...restPs] = parts.map(x => parseInt(x, 10)); return new Date(Date.UTC(firstP, secondP - 1, ...restPs)); } } const MINUTE = 60; const HOUR = MINUTE * 60; const DAY = HOUR * 24; const WEEK = DAY * 7; const MONTH = DAY * 30; const YEAR = DAY * 365; ; /** * To modify the text displayed, create a new instance of TimeagoIntl and * include it in a custom provider */ class TimeagoIntl { constructor() { /** * Stream that emits whenever the l10n strings are changed * Use this to notify directives if the l10n strings have changed after initialization. */ this.changes = new Subject(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoIntl, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoIntl }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoIntl, decorators: [{ type: Injectable }] }); const defaultFormattter = function (then) { const now = Date.now(); const seconds = Math.round(Math.abs(now - then) / 1000); const suffix = then < now ? 'ago' : 'from now'; const [value, unit] = seconds < MINUTE ? [Math.round(seconds), 'second'] : seconds < HOUR ? [Math.round(seconds / MINUTE), 'minute'] : seconds < DAY ? [Math.round(seconds / HOUR), 'hour'] : seconds < WEEK ? [Math.round(seconds / DAY), 'day'] : seconds < MONTH ? [Math.round(seconds / WEEK), 'week'] : seconds < YEAR ? [Math.round(seconds / MONTH), 'month'] : [Math.round(seconds / YEAR), 'year']; return { value, unit, suffix }; }; class TimeagoFormatter { } class TimeagoDefaultFormatter extends TimeagoFormatter { format(then) { const { suffix, value, unit } = defaultFormattter(then); return this.parse(value, unit, suffix); } parse(value, unit, suffix) { if (value !== 1) { unit += 's'; } return value + ' ' + unit + ' ' + suffix; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoDefaultFormatter, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoDefaultFormatter }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoDefaultFormatter, decorators: [{ type: Injectable }] }); class TimeagoCustomFormatter extends TimeagoFormatter { constructor(intl) { super(); this.intl = intl; } format(then) { const { suffix, value, unit } = defaultFormattter(then); return this.parse(value, unit, suffix, Date.now(), then); } parse(value, unit, suffix, now, then) { /** convert weeks to days if strings don't handle weeks */ if (unit === 'week' && !this.intl.strings.week && !this.intl.strings.weeks) { const days = Math.round(Math.abs(now - then) / (1000 * 60 * 60 * 24)); value = days; unit = 'day'; } /** create a normalize function for given value */ const normalize = this.normalizeFn(value, now - then, this.intl.strings.numbers); /** The eventual return value stored in an array so that the wordSeparator can be used */ const dateString = []; /** handle prefixes */ if (suffix === 'ago' && this.intl.strings.prefixAgo) { dateString.push(normalize(this.intl.strings.prefixAgo)); } if (suffix === 'from now' && this.intl.strings.prefixFromNow) { dateString.push(normalize(this.intl.strings.prefixFromNow)); } /** Handle Main number and unit */ const isPlural = value > 1; if (isPlural) { const stringFn = this.intl.strings[unit + 's'] || this.intl.strings[unit] || '%d ' + unit; dateString.push(normalize(stringFn)); } else { const stringFn = this.intl.strings[unit] || this.intl.strings[unit + 's'] || '%d ' + unit; dateString.push(normalize(stringFn)); } /** Handle Suffixes */ if (suffix === 'ago' && this.intl.strings.suffixAgo) { dateString.push(normalize(this.intl.strings.suffixAgo)); } if (suffix === 'from now' && this.intl.strings.suffixFromNow) { dateString.push(normalize(this.intl.strings.suffixFromNow)); } /** join the array into a string and return it */ const wordSeparator = typeof this.intl.strings.wordSeparator === 'string' ? this.intl.strings.wordSeparator : ' '; return dateString.join(wordSeparator); } /** * If the numbers array is present, format numbers with it, * otherwise just cast the number to a string and return it */ normalizeNumber(numbers, value) { return numbers && numbers.length === 10 ? String(value).split('') .map((digit) => digit.match(/^[0-9]$/) ? numbers[parseInt(digit, 10)] : digit) .join('') : String(value); } /** * Take a string or a function that takes number of days and returns a string * and provide a uniform API to create string parts */ normalizeFn(value, millisDelta, numbers) { return (stringOrFn) => typeof stringOrFn === 'function' ? stringOrFn(value, millisDelta).replace(/%d/g, this.normalizeNumber(numbers, value)) : stringOrFn.replace(/%d/g, this.normalizeNumber(numbers, value)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoCustomFormatter, deps: [{ token: TimeagoIntl }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoCustomFormatter }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoCustomFormatter, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: TimeagoIntl }]; } }); class TimeagoClock { } class TimeagoDefaultClock extends TimeagoClock { tick(then) { return of(0) .pipe(expand(() => { const now = Date.now(); const seconds = Math.round(Math.abs(now - then) / 1000); const period = seconds < MINUTE ? 1000 : seconds < HOUR ? 1000 * MINUTE : seconds < DAY ? 1000 * HOUR : 0; return period ? timer(period) : empty(); }), skip(1)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoDefaultClock, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoDefaultClock }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoDefaultClock, decorators: [{ type: Injectable }] }); class TimeagoDirective { /** The Date to display. An actual Date object or something that can be fed to new Date. */ get date() { return this._date; } set date(date) { this._date = dateParser(date).valueOf(); if (this._date) { if (this.clockSubscription) { this.clockSubscription.unsubscribe(); this.clockSubscription = undefined; } this.clockSubscription = this.clock.tick(this.date) .pipe(filter(() => this.live, this)) .subscribe(() => this.stateChanges.next()); } else { throw new SyntaxError(`Wrong parameter in TimeagoDirective. Expected a valid date, received: ${date}`); } } /** If the directive should update itself over time */ get live() { return this._live; } set live(live) { this._live = coerceBooleanProperty(live); } constructor(intl, cd, formatter, element, clock) { this.cd = cd; this.clock = clock; /** * Emits on: * - Input change * - Intl change * - Clock tick */ this.stateChanges = new Subject(); this._live = true; if (intl) { this.intlSubscription = intl.changes.subscribe(() => this.stateChanges.next()); } this.stateChanges.subscribe(() => { this.setContent(element.nativeElement, formatter.format(this.date)); this.cd.markForCheck(); }); } ngOnChanges() { this.stateChanges.next(); } setContent(node, content) { if (isDefined(node.textContent)) { node.textContent = content; } else { node.data = content; } } ngOnDestroy() { if (this.intlSubscription) { this.intlSubscription.unsubscribe(); this.intlSubscription = undefined; } if (this.clockSubscription) { this.clockSubscription.unsubscribe(); this.clockSubscription = undefined; } this.stateChanges.complete(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoDirective, deps: [{ token: TimeagoIntl, optional: true }, { token: i0.ChangeDetectorRef }, { token: TimeagoFormatter }, { token: i0.ElementRef }, { token: TimeagoClock }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: TimeagoDirective, selector: "[timeago]", inputs: { date: "date", live: "live" }, exportAs: ["timeago"], usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoDirective, decorators: [{ type: Directive, args: [{ selector: '[timeago]', exportAs: 'timeago', }] }], ctorParameters: function () { return [{ type: TimeagoIntl, decorators: [{ type: Optional }] }, { type: i0.ChangeDetectorRef }, { type: TimeagoFormatter }, { type: i0.ElementRef }, { type: TimeagoClock }]; }, propDecorators: { date: [{ type: Input }], live: [{ type: Input }] } }); class TimeagoPipe { constructor(intl, cd, formatter, clock) { this.clock = clock; this.live = true; /** * Emits on: * - Input change * - Intl change * - Clock tick */ this.stateChanges = new Subject(); if (intl) { this.intlSubscription = intl.changes.subscribe(() => this.stateChanges.next()); } this.stateChanges.subscribe(() => { this.value = formatter.format(this.date); cd.markForCheck(); }); } transform(date, ...args) { const _date = dateParser(date).valueOf(); let _live; _live = isDefined(args[0]) ? coerceBooleanProperty(args[0]) : this.live; if (this.date === _date && this.live === _live) { return this.value; } this.date = _date; this.live = _live; if (this.date) { if (this.clockSubscription) { this.clockSubscription.unsubscribe(); this.clockSubscription = undefined; } this.clockSubscription = this.clock.tick(this.date) .pipe(filter(() => this.live, this)) .subscribe(() => this.stateChanges.next()); this.stateChanges.next(); } else { throw new SyntaxError(`Wrong parameter in TimeagoPipe. Expected a valid date, received: ${date}`); } return this.value; } ngOnDestroy() { if (this.intlSubscription) { this.intlSubscription.unsubscribe(); this.intlSubscription = undefined; } if (this.clockSubscription) { this.clockSubscription.unsubscribe(); this.clockSubscription = undefined; } this.stateChanges.complete(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoPipe, deps: [{ token: TimeagoIntl, optional: true }, { token: i0.ChangeDetectorRef }, { token: TimeagoFormatter }, { token: TimeagoClock }], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "16.1.1", ngImport: i0, type: TimeagoPipe, name: "timeago", pure: false }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoPipe }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoPipe, decorators: [{ type: Injectable }, { type: Pipe, args: [{ name: 'timeago', pure: false, // required to update the value when stateChanges emits }] }], ctorParameters: function () { return [{ type: TimeagoIntl, decorators: [{ type: Optional }] }, { type: i0.ChangeDetectorRef }, { type: TimeagoFormatter }, { type: TimeagoClock }]; } }); class TimeagoModule { /** * Use this method in your root module to provide the TimeagoModule */ static forRoot(config = {}) { return { ngModule: TimeagoModule, providers: [ config.clock || { provide: TimeagoClock, useClass: TimeagoDefaultClock }, config.intl || [], config.formatter || { provide: TimeagoFormatter, useClass: TimeagoDefaultFormatter }, ], }; } /** * Use this method in your other (non root) modules to import the directive/pipe */ static forChild(config = {}) { return { ngModule: TimeagoModule, providers: [ config.clock || { provide: TimeagoClock, useClass: TimeagoDefaultClock }, config.intl || [], config.formatter || { provide: TimeagoFormatter, useClass: TimeagoDefaultFormatter }, ], }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.1.1", ngImport: i0, type: TimeagoModule, declarations: [TimeagoDirective, TimeagoPipe], exports: [TimeagoDirective, TimeagoPipe] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: TimeagoModule, decorators: [{ type: NgModule, args: [{ declarations: [ TimeagoDirective, TimeagoPipe, ], exports: [ TimeagoDirective, TimeagoPipe, ], }] }] }); /** * Generated bundle index. Do not edit. */ export { TimeagoClock, TimeagoCustomFormatter, TimeagoDefaultClock, TimeagoDefaultFormatter, TimeagoDirective, TimeagoFormatter, TimeagoIntl, TimeagoModule, TimeagoPipe }; //# sourceMappingURL=ngx-timeago.mjs.map