mirror of
https://github.com/Qortal/qortal-ui.git
synced 2025-02-12 02:05:51 +00:00
JS files
This commit is contained in:
parent
abf736c640
commit
1a2a877a9d
@ -0,0 +1,87 @@
|
|||||||
|
import { makeFormatter } from './utils';
|
||||||
|
const datetimes = new WeakMap();
|
||||||
|
export default class ExtendedTimeElement extends HTMLElement {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return [
|
||||||
|
'datetime',
|
||||||
|
'day',
|
||||||
|
'format',
|
||||||
|
'lang',
|
||||||
|
'hour',
|
||||||
|
'minute',
|
||||||
|
'month',
|
||||||
|
'second',
|
||||||
|
'title',
|
||||||
|
'weekday',
|
||||||
|
'year',
|
||||||
|
'time-zone-name'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
const title = this.getFormattedTitle();
|
||||||
|
if (title && !this.hasAttribute('title')) {
|
||||||
|
this.setAttribute('title', title);
|
||||||
|
}
|
||||||
|
const text = this.getFormattedDate();
|
||||||
|
if (text) {
|
||||||
|
this.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributeChangedCallback(attrName, oldValue, newValue) {
|
||||||
|
const oldTitle = this.getFormattedTitle();
|
||||||
|
if (attrName === 'datetime') {
|
||||||
|
const millis = Date.parse(newValue);
|
||||||
|
if (isNaN(millis)) {
|
||||||
|
datetimes.delete(this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
datetimes.set(this, new Date(millis));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const title = this.getFormattedTitle();
|
||||||
|
const currentTitle = this.getAttribute('title');
|
||||||
|
if (attrName !== 'title' && title && (!currentTitle || currentTitle === oldTitle)) {
|
||||||
|
this.setAttribute('title', title);
|
||||||
|
}
|
||||||
|
const text = this.getFormattedDate();
|
||||||
|
if (text) {
|
||||||
|
this.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get date() {
|
||||||
|
return datetimes.get(this);
|
||||||
|
}
|
||||||
|
getFormattedTitle() {
|
||||||
|
const date = this.date;
|
||||||
|
if (!date)
|
||||||
|
return;
|
||||||
|
const formatter = titleFormatter();
|
||||||
|
if (formatter) {
|
||||||
|
return formatter.format(date);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
return date.toLocaleString();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (e instanceof RangeError) {
|
||||||
|
return date.toString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getFormattedDate() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const titleFormatter = makeFormatter({
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZoneName: 'short'
|
||||||
|
});
|
705
qortal-ui-plugins/plugins/core/components/time-elements/index.js
Normal file
705
qortal-ui-plugins/plugins/core/components/time-elements/index.js
Normal file
@ -0,0 +1,705 @@
|
|||||||
|
const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||||
|
const months = [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December'
|
||||||
|
];
|
||||||
|
function pad(num) {
|
||||||
|
return `0${num}`.slice(-2);
|
||||||
|
}
|
||||||
|
function strftime(time, formatString) {
|
||||||
|
const day = time.getDay();
|
||||||
|
const date = time.getDate();
|
||||||
|
const month = time.getMonth();
|
||||||
|
const year = time.getFullYear();
|
||||||
|
const hour = time.getHours();
|
||||||
|
const minute = time.getMinutes();
|
||||||
|
const second = time.getSeconds();
|
||||||
|
return formatString.replace(/%([%aAbBcdeHIlmMpPSwyYZz])/g, function (_arg) {
|
||||||
|
let match;
|
||||||
|
const modifier = _arg[1];
|
||||||
|
switch (modifier) {
|
||||||
|
case '%':
|
||||||
|
return '%';
|
||||||
|
case 'a':
|
||||||
|
return weekdays[day].slice(0, 3);
|
||||||
|
case 'A':
|
||||||
|
return weekdays[day];
|
||||||
|
case 'b':
|
||||||
|
return months[month].slice(0, 3);
|
||||||
|
case 'B':
|
||||||
|
return months[month];
|
||||||
|
case 'c':
|
||||||
|
return time.toString();
|
||||||
|
case 'd':
|
||||||
|
return pad(date);
|
||||||
|
case 'e':
|
||||||
|
return String(date);
|
||||||
|
case 'H':
|
||||||
|
return pad(hour);
|
||||||
|
case 'I':
|
||||||
|
return pad(strftime(time, '%l'));
|
||||||
|
case 'l':
|
||||||
|
if (hour === 0 || hour === 12) {
|
||||||
|
return String(12);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return String((hour + 12) % 12);
|
||||||
|
}
|
||||||
|
case 'm':
|
||||||
|
return pad(month + 1);
|
||||||
|
case 'M':
|
||||||
|
return pad(minute);
|
||||||
|
case 'p':
|
||||||
|
if (hour > 11) {
|
||||||
|
return 'PM';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'AM';
|
||||||
|
}
|
||||||
|
case 'P':
|
||||||
|
if (hour > 11) {
|
||||||
|
return 'pm';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'am';
|
||||||
|
}
|
||||||
|
case 'S':
|
||||||
|
return pad(second);
|
||||||
|
case 'w':
|
||||||
|
return String(day);
|
||||||
|
case 'y':
|
||||||
|
return pad(year % 100);
|
||||||
|
case 'Y':
|
||||||
|
return String(year);
|
||||||
|
case 'Z':
|
||||||
|
match = time.toString().match(/\((\w+)\)$/);
|
||||||
|
return match ? match[1] : '';
|
||||||
|
case 'z':
|
||||||
|
match = time.toString().match(/\w([+-]\d\d\d\d) /);
|
||||||
|
return match ? match[1] : '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function makeFormatter(options) {
|
||||||
|
let format;
|
||||||
|
return function () {
|
||||||
|
if (format)
|
||||||
|
return format;
|
||||||
|
if ('Intl' in window) {
|
||||||
|
try {
|
||||||
|
format = new Intl.DateTimeFormat(undefined, options);
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (!(e instanceof RangeError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let dayFirst = null;
|
||||||
|
const dayFirstFormatter = makeFormatter({ day: 'numeric', month: 'short' });
|
||||||
|
function isDayFirst() {
|
||||||
|
if (dayFirst !== null) {
|
||||||
|
return dayFirst;
|
||||||
|
}
|
||||||
|
const formatter = dayFirstFormatter();
|
||||||
|
if (formatter) {
|
||||||
|
const output = formatter.format(new Date(0));
|
||||||
|
dayFirst = !!output.match(/^\d/);
|
||||||
|
return dayFirst;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let yearSeparator = null;
|
||||||
|
const yearFormatter = makeFormatter({ day: 'numeric', month: 'short', year: 'numeric' });
|
||||||
|
function isYearSeparator() {
|
||||||
|
if (yearSeparator !== null) {
|
||||||
|
return yearSeparator;
|
||||||
|
}
|
||||||
|
const formatter = yearFormatter();
|
||||||
|
if (formatter) {
|
||||||
|
const output = formatter.format(new Date(0));
|
||||||
|
yearSeparator = !!output.match(/\d,/);
|
||||||
|
return yearSeparator;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isThisYear(date) {
|
||||||
|
const now = new Date();
|
||||||
|
return now.getUTCFullYear() === date.getUTCFullYear();
|
||||||
|
}
|
||||||
|
function makeRelativeFormat(locale, options) {
|
||||||
|
if ('Intl' in window && 'RelativeTimeFormat' in window.Intl) {
|
||||||
|
try {
|
||||||
|
return new Intl.RelativeTimeFormat(locale, options);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (!(e instanceof RangeError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function localeFromElement(el) {
|
||||||
|
const container = el.closest('[lang]');
|
||||||
|
if (container instanceof HTMLElement && container.lang) {
|
||||||
|
return container.lang;
|
||||||
|
}
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
const datetimes = new WeakMap();
|
||||||
|
class ExtendedTimeElement extends HTMLElement {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return [
|
||||||
|
'datetime',
|
||||||
|
'day',
|
||||||
|
'format',
|
||||||
|
'lang',
|
||||||
|
'hour',
|
||||||
|
'minute',
|
||||||
|
'month',
|
||||||
|
'second',
|
||||||
|
'title',
|
||||||
|
'weekday',
|
||||||
|
'year',
|
||||||
|
'time-zone-name'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
const title = this.getFormattedTitle();
|
||||||
|
if (title && !this.hasAttribute('title')) {
|
||||||
|
this.setAttribute('title', title);
|
||||||
|
}
|
||||||
|
const text = this.getFormattedDate();
|
||||||
|
if (text) {
|
||||||
|
this.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributeChangedCallback(attrName, oldValue, newValue) {
|
||||||
|
const oldTitle = this.getFormattedTitle();
|
||||||
|
if (attrName === 'datetime') {
|
||||||
|
const millis = Date.parse(newValue);
|
||||||
|
if (isNaN(millis)) {
|
||||||
|
datetimes.delete(this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
datetimes.set(this, new Date(millis));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const title = this.getFormattedTitle();
|
||||||
|
const currentTitle = this.getAttribute('title');
|
||||||
|
if (attrName !== 'title' && title && (!currentTitle || currentTitle === oldTitle)) {
|
||||||
|
this.setAttribute('title', title);
|
||||||
|
}
|
||||||
|
const text = this.getFormattedDate();
|
||||||
|
if (text) {
|
||||||
|
this.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get date() {
|
||||||
|
return datetimes.get(this);
|
||||||
|
}
|
||||||
|
getFormattedTitle() {
|
||||||
|
const date = this.date;
|
||||||
|
if (!date)
|
||||||
|
return;
|
||||||
|
const formatter = titleFormatter();
|
||||||
|
if (formatter) {
|
||||||
|
return formatter.format(date);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
return date.toLocaleString();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (e instanceof RangeError) {
|
||||||
|
return date.toString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getFormattedDate() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const titleFormatter = makeFormatter({
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZoneName: 'short'
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatters = new WeakMap();
|
||||||
|
class LocalTimeElement extends ExtendedTimeElement {
|
||||||
|
attributeChangedCallback(attrName, oldValue, newValue) {
|
||||||
|
if (attrName === 'hour' || attrName === 'minute' || attrName === 'second' || attrName === 'time-zone-name') {
|
||||||
|
formatters.delete(this);
|
||||||
|
}
|
||||||
|
super.attributeChangedCallback(attrName, oldValue, newValue);
|
||||||
|
}
|
||||||
|
getFormattedDate() {
|
||||||
|
const d = this.date;
|
||||||
|
if (!d)
|
||||||
|
return;
|
||||||
|
const date = formatDate(this, d) || '';
|
||||||
|
const time = formatTime(this, d) || '';
|
||||||
|
return `${date} ${time}`.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function formatDate(el, date) {
|
||||||
|
const props = {
|
||||||
|
weekday: {
|
||||||
|
short: '%a',
|
||||||
|
long: '%A'
|
||||||
|
},
|
||||||
|
day: {
|
||||||
|
numeric: '%e',
|
||||||
|
'2-digit': '%d'
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
short: '%b',
|
||||||
|
long: '%B'
|
||||||
|
},
|
||||||
|
year: {
|
||||||
|
numeric: '%Y',
|
||||||
|
'2-digit': '%y'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let format = isDayFirst() ? 'weekday day month year' : 'weekday month day, year';
|
||||||
|
for (const prop in props) {
|
||||||
|
const value = props[prop][el.getAttribute(prop) || ''];
|
||||||
|
format = format.replace(prop, value || '');
|
||||||
|
}
|
||||||
|
format = format.replace(/(\s,)|(,\s$)/, '');
|
||||||
|
return strftime(date, format).replace(/\s+/, ' ').trim();
|
||||||
|
}
|
||||||
|
function formatTime(el, date) {
|
||||||
|
const options = {};
|
||||||
|
const hour = el.getAttribute('hour');
|
||||||
|
if (hour === 'numeric' || hour === '2-digit')
|
||||||
|
options.hour = hour;
|
||||||
|
const minute = el.getAttribute('minute');
|
||||||
|
if (minute === 'numeric' || minute === '2-digit')
|
||||||
|
options.minute = minute;
|
||||||
|
const second = el.getAttribute('second');
|
||||||
|
if (second === 'numeric' || second === '2-digit')
|
||||||
|
options.second = second;
|
||||||
|
const tz = el.getAttribute('time-zone-name');
|
||||||
|
if (tz === 'short' || tz === 'long')
|
||||||
|
options.timeZoneName = tz;
|
||||||
|
if (Object.keys(options).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let factory = formatters.get(el);
|
||||||
|
if (!factory) {
|
||||||
|
factory = makeFormatter(options);
|
||||||
|
formatters.set(el, factory);
|
||||||
|
}
|
||||||
|
const formatter = factory();
|
||||||
|
if (formatter) {
|
||||||
|
return formatter.format(date);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const timef = options.second ? '%H:%M:%S' : '%H:%M';
|
||||||
|
return strftime(date, timef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!window.customElements.get('local-time')) {
|
||||||
|
window.LocalTimeElement = LocalTimeElement;
|
||||||
|
window.customElements.define('local-time', LocalTimeElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RelativeTime {
|
||||||
|
constructor(date, locale) {
|
||||||
|
this.date = date;
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
const ago = this.timeElapsed();
|
||||||
|
if (ago) {
|
||||||
|
return ago;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const ahead = this.timeAhead();
|
||||||
|
if (ahead) {
|
||||||
|
return ahead;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `on ${this.formatDate()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeElapsed() {
|
||||||
|
const ms = new Date().getTime() - this.date.getTime();
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
if (ms >= 0 && day < 30) {
|
||||||
|
return this.timeAgoFromMs(ms);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeAhead() {
|
||||||
|
const ms = this.date.getTime() - new Date().getTime();
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
if (ms >= 0 && day < 30) {
|
||||||
|
return this.timeUntil();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeAgo() {
|
||||||
|
const ms = new Date().getTime() - this.date.getTime();
|
||||||
|
return this.timeAgoFromMs(ms);
|
||||||
|
}
|
||||||
|
timeAgoFromMs(ms) {
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
const month = Math.round(day / 30);
|
||||||
|
const year = Math.round(month / 12);
|
||||||
|
if (ms < 0) {
|
||||||
|
return formatRelativeTime(this.locale, 0, 'second');
|
||||||
|
}
|
||||||
|
else if (sec < 10) {
|
||||||
|
return formatRelativeTime(this.locale, 0, 'second');
|
||||||
|
}
|
||||||
|
else if (sec < 45) {
|
||||||
|
return formatRelativeTime(this.locale, -sec, 'second');
|
||||||
|
}
|
||||||
|
else if (sec < 90) {
|
||||||
|
return formatRelativeTime(this.locale, -min, 'minute');
|
||||||
|
}
|
||||||
|
else if (min < 45) {
|
||||||
|
return formatRelativeTime(this.locale, -min, 'minute');
|
||||||
|
}
|
||||||
|
else if (min < 90) {
|
||||||
|
return formatRelativeTime(this.locale, -hr, 'hour');
|
||||||
|
}
|
||||||
|
else if (hr < 24) {
|
||||||
|
return formatRelativeTime(this.locale, -hr, 'hour');
|
||||||
|
}
|
||||||
|
else if (hr < 36) {
|
||||||
|
return formatRelativeTime(this.locale, -day, 'day');
|
||||||
|
}
|
||||||
|
else if (day < 30) {
|
||||||
|
return formatRelativeTime(this.locale, -day, 'day');
|
||||||
|
}
|
||||||
|
else if (month < 18) {
|
||||||
|
return formatRelativeTime(this.locale, -month, 'month');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return formatRelativeTime(this.locale, -year, 'year');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
microTimeAgo() {
|
||||||
|
const ms = new Date().getTime() - this.date.getTime();
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
const month = Math.round(day / 30);
|
||||||
|
const year = Math.round(month / 12);
|
||||||
|
if (min < 1) {
|
||||||
|
return '1m';
|
||||||
|
}
|
||||||
|
else if (min < 60) {
|
||||||
|
return `${min}m`;
|
||||||
|
}
|
||||||
|
else if (hr < 24) {
|
||||||
|
return `${hr}h`;
|
||||||
|
}
|
||||||
|
else if (day < 365) {
|
||||||
|
return `${day}d`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `${year}y`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeUntil() {
|
||||||
|
const ms = this.date.getTime() - new Date().getTime();
|
||||||
|
return this.timeUntilFromMs(ms);
|
||||||
|
}
|
||||||
|
timeUntilFromMs(ms) {
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
const month = Math.round(day / 30);
|
||||||
|
const year = Math.round(month / 12);
|
||||||
|
if (month >= 18) {
|
||||||
|
return formatRelativeTime(this.locale, year, 'year');
|
||||||
|
}
|
||||||
|
else if (month >= 12) {
|
||||||
|
return formatRelativeTime(this.locale, year, 'year');
|
||||||
|
}
|
||||||
|
else if (day >= 45) {
|
||||||
|
return formatRelativeTime(this.locale, month, 'month');
|
||||||
|
}
|
||||||
|
else if (day >= 30) {
|
||||||
|
return formatRelativeTime(this.locale, month, 'month');
|
||||||
|
}
|
||||||
|
else if (hr >= 36) {
|
||||||
|
return formatRelativeTime(this.locale, day, 'day');
|
||||||
|
}
|
||||||
|
else if (hr >= 24) {
|
||||||
|
return formatRelativeTime(this.locale, day, 'day');
|
||||||
|
}
|
||||||
|
else if (min >= 90) {
|
||||||
|
return formatRelativeTime(this.locale, hr, 'hour');
|
||||||
|
}
|
||||||
|
else if (min >= 45) {
|
||||||
|
return formatRelativeTime(this.locale, hr, 'hour');
|
||||||
|
}
|
||||||
|
else if (sec >= 90) {
|
||||||
|
return formatRelativeTime(this.locale, min, 'minute');
|
||||||
|
}
|
||||||
|
else if (sec >= 45) {
|
||||||
|
return formatRelativeTime(this.locale, min, 'minute');
|
||||||
|
}
|
||||||
|
else if (sec >= 10) {
|
||||||
|
return formatRelativeTime(this.locale, sec, 'second');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return formatRelativeTime(this.locale, 0, 'second');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
microTimeUntil() {
|
||||||
|
const ms = this.date.getTime() - new Date().getTime();
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
const month = Math.round(day / 30);
|
||||||
|
const year = Math.round(month / 12);
|
||||||
|
if (day >= 365) {
|
||||||
|
return `${year}y`;
|
||||||
|
}
|
||||||
|
else if (hr >= 24) {
|
||||||
|
return `${day}d`;
|
||||||
|
}
|
||||||
|
else if (min >= 60) {
|
||||||
|
return `${hr}h`;
|
||||||
|
}
|
||||||
|
else if (min > 1) {
|
||||||
|
return `${min}m`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return '1m';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formatDate() {
|
||||||
|
let format = isDayFirst() ? '%e %b' : '%b %e';
|
||||||
|
if (!isThisYear(this.date)) {
|
||||||
|
format += isYearSeparator() ? ', %Y' : ' %Y';
|
||||||
|
}
|
||||||
|
return strftime(this.date, format);
|
||||||
|
}
|
||||||
|
formatTime() {
|
||||||
|
const formatter = timeFormatter();
|
||||||
|
if (formatter) {
|
||||||
|
return formatter.format(this.date);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return strftime(this.date, '%l:%M%P');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function formatRelativeTime(locale, value, unit) {
|
||||||
|
const formatter = makeRelativeFormat(locale, { numeric: 'auto' });
|
||||||
|
if (formatter) {
|
||||||
|
return formatter.format(value, unit);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return formatEnRelativeTime(value, unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function formatEnRelativeTime(value, unit) {
|
||||||
|
if (value === 0) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
return `this ${unit}`;
|
||||||
|
case 'day':
|
||||||
|
return 'today';
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
return `in 0 ${unit}s`;
|
||||||
|
case 'second':
|
||||||
|
return 'now';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value === 1) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
return `next ${unit}`;
|
||||||
|
case 'day':
|
||||||
|
return 'tomorrow';
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
case 'second':
|
||||||
|
return `in 1 ${unit}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value === -1) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
return `last ${unit}`;
|
||||||
|
case 'day':
|
||||||
|
return 'yesterday';
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
case 'second':
|
||||||
|
return `1 ${unit} ago`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value > 1) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
case 'day':
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
case 'second':
|
||||||
|
return `in ${value} ${unit}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value < -1) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
case 'day':
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
case 'second':
|
||||||
|
return `${-value} ${unit}s ago`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RangeError(`Invalid unit argument for format() '${unit}'`);
|
||||||
|
}
|
||||||
|
const timeFormatter = makeFormatter({ hour: 'numeric', minute: '2-digit' });
|
||||||
|
|
||||||
|
class RelativeTimeElement extends ExtendedTimeElement {
|
||||||
|
getFormattedDate() {
|
||||||
|
const date = this.date;
|
||||||
|
if (!date)
|
||||||
|
return;
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).toString();
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
nowElements.push(this);
|
||||||
|
if (!updateNowElementsId) {
|
||||||
|
updateNowElements();
|
||||||
|
updateNowElementsId = window.setInterval(updateNowElements, 60 * 1000);
|
||||||
|
}
|
||||||
|
super.connectedCallback();
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
const ix = nowElements.indexOf(this);
|
||||||
|
if (ix !== -1) {
|
||||||
|
nowElements.splice(ix, 1);
|
||||||
|
}
|
||||||
|
if (!nowElements.length) {
|
||||||
|
if (updateNowElementsId) {
|
||||||
|
clearInterval(updateNowElementsId);
|
||||||
|
updateNowElementsId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const nowElements = [];
|
||||||
|
let updateNowElementsId;
|
||||||
|
function updateNowElements() {
|
||||||
|
let time, i, len;
|
||||||
|
for (i = 0, len = nowElements.length; i < len; i++) {
|
||||||
|
time = nowElements[i];
|
||||||
|
time.textContent = time.getFormattedDate() || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!window.customElements.get('relative-time')) {
|
||||||
|
window.RelativeTimeElement = RelativeTimeElement;
|
||||||
|
window.customElements.define('relative-time', RelativeTimeElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeAgoElement extends RelativeTimeElement {
|
||||||
|
getFormattedDate() {
|
||||||
|
const format = this.getAttribute('format');
|
||||||
|
const date = this.date;
|
||||||
|
if (!date)
|
||||||
|
return;
|
||||||
|
if (format === 'micro') {
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).microTimeAgo();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).timeAgo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!window.customElements.get('time-ago')) {
|
||||||
|
window.TimeAgoElement = TimeAgoElement;
|
||||||
|
window.customElements.define('time-ago', TimeAgoElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeUntilElement extends RelativeTimeElement {
|
||||||
|
getFormattedDate() {
|
||||||
|
const format = this.getAttribute('format');
|
||||||
|
const date = this.date;
|
||||||
|
if (!date)
|
||||||
|
return;
|
||||||
|
if (format === 'micro') {
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).microTimeUntil();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).timeUntil();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!window.customElements.get('time-until')) {
|
||||||
|
window.TimeUntilElement = TimeUntilElement;
|
||||||
|
window.customElements.define('time-until', TimeUntilElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { LocalTimeElement, RelativeTimeElement, TimeAgoElement, TimeUntilElement };
|
@ -0,0 +1,81 @@
|
|||||||
|
import { strftime, makeFormatter, isDayFirst } from './utils';
|
||||||
|
import ExtendedTimeElement from './extended-time-element';
|
||||||
|
const formatters = new WeakMap();
|
||||||
|
export default class LocalTimeElement extends ExtendedTimeElement {
|
||||||
|
attributeChangedCallback(attrName, oldValue, newValue) {
|
||||||
|
if (attrName === 'hour' || attrName === 'minute' || attrName === 'second' || attrName === 'time-zone-name') {
|
||||||
|
formatters.delete(this);
|
||||||
|
}
|
||||||
|
super.attributeChangedCallback(attrName, oldValue, newValue);
|
||||||
|
}
|
||||||
|
getFormattedDate() {
|
||||||
|
const d = this.date;
|
||||||
|
if (!d)
|
||||||
|
return;
|
||||||
|
const date = formatDate(this, d) || '';
|
||||||
|
const time = formatTime(this, d) || '';
|
||||||
|
return `${date} ${time}`.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function formatDate(el, date) {
|
||||||
|
const props = {
|
||||||
|
weekday: {
|
||||||
|
short: '%a',
|
||||||
|
long: '%A'
|
||||||
|
},
|
||||||
|
day: {
|
||||||
|
numeric: '%e',
|
||||||
|
'2-digit': '%d'
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
short: '%b',
|
||||||
|
long: '%B'
|
||||||
|
},
|
||||||
|
year: {
|
||||||
|
numeric: '%Y',
|
||||||
|
'2-digit': '%y'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let format = isDayFirst() ? 'weekday day month year' : 'weekday month day, year';
|
||||||
|
for (const prop in props) {
|
||||||
|
const value = props[prop][el.getAttribute(prop) || ''];
|
||||||
|
format = format.replace(prop, value || '');
|
||||||
|
}
|
||||||
|
format = format.replace(/(\s,)|(,\s$)/, '');
|
||||||
|
return strftime(date, format).replace(/\s+/, ' ').trim();
|
||||||
|
}
|
||||||
|
function formatTime(el, date) {
|
||||||
|
const options = {};
|
||||||
|
const hour = el.getAttribute('hour');
|
||||||
|
if (hour === 'numeric' || hour === '2-digit')
|
||||||
|
options.hour = hour;
|
||||||
|
const minute = el.getAttribute('minute');
|
||||||
|
if (minute === 'numeric' || minute === '2-digit')
|
||||||
|
options.minute = minute;
|
||||||
|
const second = el.getAttribute('second');
|
||||||
|
if (second === 'numeric' || second === '2-digit')
|
||||||
|
options.second = second;
|
||||||
|
const tz = el.getAttribute('time-zone-name');
|
||||||
|
if (tz === 'short' || tz === 'long')
|
||||||
|
options.timeZoneName = tz;
|
||||||
|
if (Object.keys(options).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let factory = formatters.get(el);
|
||||||
|
if (!factory) {
|
||||||
|
factory = makeFormatter(options);
|
||||||
|
formatters.set(el, factory);
|
||||||
|
}
|
||||||
|
const formatter = factory();
|
||||||
|
if (formatter) {
|
||||||
|
return formatter.format(date);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const timef = options.second ? '%H:%M:%S' : '%H:%M';
|
||||||
|
return strftime(date, timef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!window.customElements.get('local-time')) {
|
||||||
|
window.LocalTimeElement = LocalTimeElement;
|
||||||
|
window.customElements.define('local-time', LocalTimeElement);
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import RelativeTime from './relative-time';
|
||||||
|
import ExtendedTimeElement from './extended-time-element';
|
||||||
|
import { localeFromElement } from './utils';
|
||||||
|
export default class RelativeTimeElement extends ExtendedTimeElement {
|
||||||
|
getFormattedDate() {
|
||||||
|
const date = this.date;
|
||||||
|
if (!date)
|
||||||
|
return;
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).toString();
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
nowElements.push(this);
|
||||||
|
if (!updateNowElementsId) {
|
||||||
|
updateNowElements();
|
||||||
|
updateNowElementsId = window.setInterval(updateNowElements, 60 * 1000);
|
||||||
|
}
|
||||||
|
super.connectedCallback();
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
const ix = nowElements.indexOf(this);
|
||||||
|
if (ix !== -1) {
|
||||||
|
nowElements.splice(ix, 1);
|
||||||
|
}
|
||||||
|
if (!nowElements.length) {
|
||||||
|
if (updateNowElementsId) {
|
||||||
|
clearInterval(updateNowElementsId);
|
||||||
|
updateNowElementsId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const nowElements = [];
|
||||||
|
let updateNowElementsId;
|
||||||
|
function updateNowElements() {
|
||||||
|
let time, i, len;
|
||||||
|
for (i = 0, len = nowElements.length; i < len; i++) {
|
||||||
|
time = nowElements[i];
|
||||||
|
time.textContent = time.getFormattedDate() || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!window.customElements.get('relative-time')) {
|
||||||
|
window.RelativeTimeElement = RelativeTimeElement;
|
||||||
|
window.customElements.define('relative-time', RelativeTimeElement);
|
||||||
|
}
|
@ -0,0 +1,290 @@
|
|||||||
|
import { strftime, makeFormatter, makeRelativeFormat, isDayFirst, isThisYear, isYearSeparator } from './utils';
|
||||||
|
export default class RelativeTime {
|
||||||
|
constructor(date, locale) {
|
||||||
|
this.date = date;
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
const ago = this.timeElapsed();
|
||||||
|
if (ago) {
|
||||||
|
return ago;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const ahead = this.timeAhead();
|
||||||
|
if (ahead) {
|
||||||
|
return ahead;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `on ${this.formatDate()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeElapsed() {
|
||||||
|
const ms = new Date().getTime() - this.date.getTime();
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
if (ms >= 0 && day < 30) {
|
||||||
|
return this.timeAgoFromMs(ms);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeAhead() {
|
||||||
|
const ms = this.date.getTime() - new Date().getTime();
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
if (ms >= 0 && day < 30) {
|
||||||
|
return this.timeUntil();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeAgo() {
|
||||||
|
const ms = new Date().getTime() - this.date.getTime();
|
||||||
|
return this.timeAgoFromMs(ms);
|
||||||
|
}
|
||||||
|
timeAgoFromMs(ms) {
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
const month = Math.round(day / 30);
|
||||||
|
const year = Math.round(month / 12);
|
||||||
|
if (ms < 0) {
|
||||||
|
return formatRelativeTime(this.locale, 0, 'second');
|
||||||
|
}
|
||||||
|
else if (sec < 10) {
|
||||||
|
return formatRelativeTime(this.locale, 0, 'second');
|
||||||
|
}
|
||||||
|
else if (sec < 45) {
|
||||||
|
return formatRelativeTime(this.locale, -sec, 'second');
|
||||||
|
}
|
||||||
|
else if (sec < 90) {
|
||||||
|
return formatRelativeTime(this.locale, -min, 'minute');
|
||||||
|
}
|
||||||
|
else if (min < 45) {
|
||||||
|
return formatRelativeTime(this.locale, -min, 'minute');
|
||||||
|
}
|
||||||
|
else if (min < 90) {
|
||||||
|
return formatRelativeTime(this.locale, -hr, 'hour');
|
||||||
|
}
|
||||||
|
else if (hr < 24) {
|
||||||
|
return formatRelativeTime(this.locale, -hr, 'hour');
|
||||||
|
}
|
||||||
|
else if (hr < 36) {
|
||||||
|
return formatRelativeTime(this.locale, -day, 'day');
|
||||||
|
}
|
||||||
|
else if (day < 30) {
|
||||||
|
return formatRelativeTime(this.locale, -day, 'day');
|
||||||
|
}
|
||||||
|
else if (month < 18) {
|
||||||
|
return formatRelativeTime(this.locale, -month, 'month');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return formatRelativeTime(this.locale, -year, 'year');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
microTimeAgo() {
|
||||||
|
const ms = new Date().getTime() - this.date.getTime();
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
const month = Math.round(day / 30);
|
||||||
|
const year = Math.round(month / 12);
|
||||||
|
if (min < 1) {
|
||||||
|
return '1m';
|
||||||
|
}
|
||||||
|
else if (min < 60) {
|
||||||
|
return `${min}m`;
|
||||||
|
}
|
||||||
|
else if (hr < 24) {
|
||||||
|
return `${hr}h`;
|
||||||
|
}
|
||||||
|
else if (day < 365) {
|
||||||
|
return `${day}d`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `${year}y`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeUntil() {
|
||||||
|
const ms = this.date.getTime() - new Date().getTime();
|
||||||
|
return this.timeUntilFromMs(ms);
|
||||||
|
}
|
||||||
|
timeUntilFromMs(ms) {
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
const month = Math.round(day / 30);
|
||||||
|
const year = Math.round(month / 12);
|
||||||
|
if (month >= 18) {
|
||||||
|
return formatRelativeTime(this.locale, year, 'year');
|
||||||
|
}
|
||||||
|
else if (month >= 12) {
|
||||||
|
return formatRelativeTime(this.locale, year, 'year');
|
||||||
|
}
|
||||||
|
else if (day >= 45) {
|
||||||
|
return formatRelativeTime(this.locale, month, 'month');
|
||||||
|
}
|
||||||
|
else if (day >= 30) {
|
||||||
|
return formatRelativeTime(this.locale, month, 'month');
|
||||||
|
}
|
||||||
|
else if (hr >= 36) {
|
||||||
|
return formatRelativeTime(this.locale, day, 'day');
|
||||||
|
}
|
||||||
|
else if (hr >= 24) {
|
||||||
|
return formatRelativeTime(this.locale, day, 'day');
|
||||||
|
}
|
||||||
|
else if (min >= 90) {
|
||||||
|
return formatRelativeTime(this.locale, hr, 'hour');
|
||||||
|
}
|
||||||
|
else if (min >= 45) {
|
||||||
|
return formatRelativeTime(this.locale, hr, 'hour');
|
||||||
|
}
|
||||||
|
else if (sec >= 90) {
|
||||||
|
return formatRelativeTime(this.locale, min, 'minute');
|
||||||
|
}
|
||||||
|
else if (sec >= 45) {
|
||||||
|
return formatRelativeTime(this.locale, min, 'minute');
|
||||||
|
}
|
||||||
|
else if (sec >= 10) {
|
||||||
|
return formatRelativeTime(this.locale, sec, 'second');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return formatRelativeTime(this.locale, 0, 'second');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
microTimeUntil() {
|
||||||
|
const ms = this.date.getTime() - new Date().getTime();
|
||||||
|
const sec = Math.round(ms / 1000);
|
||||||
|
const min = Math.round(sec / 60);
|
||||||
|
const hr = Math.round(min / 60);
|
||||||
|
const day = Math.round(hr / 24);
|
||||||
|
const month = Math.round(day / 30);
|
||||||
|
const year = Math.round(month / 12);
|
||||||
|
if (day >= 365) {
|
||||||
|
return `${year}y`;
|
||||||
|
}
|
||||||
|
else if (hr >= 24) {
|
||||||
|
return `${day}d`;
|
||||||
|
}
|
||||||
|
else if (min >= 60) {
|
||||||
|
return `${hr}h`;
|
||||||
|
}
|
||||||
|
else if (min > 1) {
|
||||||
|
return `${min}m`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return '1m';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formatDate() {
|
||||||
|
let format = isDayFirst() ? '%e %b' : '%b %e';
|
||||||
|
if (!isThisYear(this.date)) {
|
||||||
|
format += isYearSeparator() ? ', %Y' : ' %Y';
|
||||||
|
}
|
||||||
|
return strftime(this.date, format);
|
||||||
|
}
|
||||||
|
formatTime() {
|
||||||
|
const formatter = timeFormatter();
|
||||||
|
if (formatter) {
|
||||||
|
return formatter.format(this.date);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return strftime(this.date, '%l:%M%P');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function formatRelativeTime(locale, value, unit) {
|
||||||
|
const formatter = makeRelativeFormat(locale, { numeric: 'auto' });
|
||||||
|
if (formatter) {
|
||||||
|
return formatter.format(value, unit);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return formatEnRelativeTime(value, unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function formatEnRelativeTime(value, unit) {
|
||||||
|
if (value === 0) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
return `this ${unit}`;
|
||||||
|
case 'day':
|
||||||
|
return 'today';
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
return `in 0 ${unit}s`;
|
||||||
|
case 'second':
|
||||||
|
return 'now';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value === 1) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
return `next ${unit}`;
|
||||||
|
case 'day':
|
||||||
|
return 'tomorrow';
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
case 'second':
|
||||||
|
return `in 1 ${unit}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value === -1) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
return `last ${unit}`;
|
||||||
|
case 'day':
|
||||||
|
return 'yesterday';
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
case 'second':
|
||||||
|
return `1 ${unit} ago`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value > 1) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
case 'day':
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
case 'second':
|
||||||
|
return `in ${value} ${unit}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value < -1) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'year':
|
||||||
|
case 'quarter':
|
||||||
|
case 'month':
|
||||||
|
case 'week':
|
||||||
|
case 'day':
|
||||||
|
case 'hour':
|
||||||
|
case 'minute':
|
||||||
|
case 'second':
|
||||||
|
return `${-value} ${unit}s ago`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RangeError(`Invalid unit argument for format() '${unit}'`);
|
||||||
|
}
|
||||||
|
const timeFormatter = makeFormatter({ hour: 'numeric', minute: '2-digit' });
|
@ -0,0 +1,21 @@
|
|||||||
|
import RelativeTime from './relative-time';
|
||||||
|
import RelativeTimeElement from './relative-time-element';
|
||||||
|
import { localeFromElement } from './utils';
|
||||||
|
export default class TimeAgoElement extends RelativeTimeElement {
|
||||||
|
getFormattedDate() {
|
||||||
|
const format = this.getAttribute('format');
|
||||||
|
const date = this.date;
|
||||||
|
if (!date)
|
||||||
|
return;
|
||||||
|
if (format === 'micro') {
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).microTimeAgo();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).timeAgo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!window.customElements.get('time-ago')) {
|
||||||
|
window.TimeAgoElement = TimeAgoElement;
|
||||||
|
window.customElements.define('time-ago', TimeAgoElement);
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import RelativeTime from './relative-time';
|
||||||
|
import RelativeTimeElement from './relative-time-element';
|
||||||
|
import { localeFromElement } from './utils';
|
||||||
|
export default class TimeUntilElement extends RelativeTimeElement {
|
||||||
|
getFormattedDate() {
|
||||||
|
const format = this.getAttribute('format');
|
||||||
|
const date = this.date;
|
||||||
|
if (!date)
|
||||||
|
return;
|
||||||
|
if (format === 'micro') {
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).microTimeUntil();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new RelativeTime(date, localeFromElement(this)).timeUntil();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!window.customElements.get('time-until')) {
|
||||||
|
window.TimeUntilElement = TimeUntilElement;
|
||||||
|
window.customElements.define('time-until', TimeUntilElement);
|
||||||
|
}
|
166
qortal-ui-plugins/plugins/core/components/time-elements/utils.js
Normal file
166
qortal-ui-plugins/plugins/core/components/time-elements/utils.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||||
|
const months = [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December'
|
||||||
|
];
|
||||||
|
function pad(num) {
|
||||||
|
return `0${num}`.slice(-2);
|
||||||
|
}
|
||||||
|
export function strftime(time, formatString) {
|
||||||
|
const day = time.getDay();
|
||||||
|
const date = time.getDate();
|
||||||
|
const month = time.getMonth();
|
||||||
|
const year = time.getFullYear();
|
||||||
|
const hour = time.getHours();
|
||||||
|
const minute = time.getMinutes();
|
||||||
|
const second = time.getSeconds();
|
||||||
|
return formatString.replace(/%([%aAbBcdeHIlmMpPSwyYZz])/g, function (_arg) {
|
||||||
|
let match;
|
||||||
|
const modifier = _arg[1];
|
||||||
|
switch (modifier) {
|
||||||
|
case '%':
|
||||||
|
return '%';
|
||||||
|
case 'a':
|
||||||
|
return weekdays[day].slice(0, 3);
|
||||||
|
case 'A':
|
||||||
|
return weekdays[day];
|
||||||
|
case 'b':
|
||||||
|
return months[month].slice(0, 3);
|
||||||
|
case 'B':
|
||||||
|
return months[month];
|
||||||
|
case 'c':
|
||||||
|
return time.toString();
|
||||||
|
case 'd':
|
||||||
|
return pad(date);
|
||||||
|
case 'e':
|
||||||
|
return String(date);
|
||||||
|
case 'H':
|
||||||
|
return pad(hour);
|
||||||
|
case 'I':
|
||||||
|
return pad(strftime(time, '%l'));
|
||||||
|
case 'l':
|
||||||
|
if (hour === 0 || hour === 12) {
|
||||||
|
return String(12);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return String((hour + 12) % 12);
|
||||||
|
}
|
||||||
|
case 'm':
|
||||||
|
return pad(month + 1);
|
||||||
|
case 'M':
|
||||||
|
return pad(minute);
|
||||||
|
case 'p':
|
||||||
|
if (hour > 11) {
|
||||||
|
return 'PM';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'AM';
|
||||||
|
}
|
||||||
|
case 'P':
|
||||||
|
if (hour > 11) {
|
||||||
|
return 'pm';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'am';
|
||||||
|
}
|
||||||
|
case 'S':
|
||||||
|
return pad(second);
|
||||||
|
case 'w':
|
||||||
|
return String(day);
|
||||||
|
case 'y':
|
||||||
|
return pad(year % 100);
|
||||||
|
case 'Y':
|
||||||
|
return String(year);
|
||||||
|
case 'Z':
|
||||||
|
match = time.toString().match(/\((\w+)\)$/);
|
||||||
|
return match ? match[1] : '';
|
||||||
|
case 'z':
|
||||||
|
match = time.toString().match(/\w([+-]\d\d\d\d) /);
|
||||||
|
return match ? match[1] : '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function makeFormatter(options) {
|
||||||
|
let format;
|
||||||
|
return function () {
|
||||||
|
if (format)
|
||||||
|
return format;
|
||||||
|
if ('Intl' in window) {
|
||||||
|
try {
|
||||||
|
format = new Intl.DateTimeFormat(undefined, options);
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (!(e instanceof RangeError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let dayFirst = null;
|
||||||
|
const dayFirstFormatter = makeFormatter({ day: 'numeric', month: 'short' });
|
||||||
|
export function isDayFirst() {
|
||||||
|
if (dayFirst !== null) {
|
||||||
|
return dayFirst;
|
||||||
|
}
|
||||||
|
const formatter = dayFirstFormatter();
|
||||||
|
if (formatter) {
|
||||||
|
const output = formatter.format(new Date(0));
|
||||||
|
dayFirst = !!output.match(/^\d/);
|
||||||
|
return dayFirst;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let yearSeparator = null;
|
||||||
|
const yearFormatter = makeFormatter({ day: 'numeric', month: 'short', year: 'numeric' });
|
||||||
|
export function isYearSeparator() {
|
||||||
|
if (yearSeparator !== null) {
|
||||||
|
return yearSeparator;
|
||||||
|
}
|
||||||
|
const formatter = yearFormatter();
|
||||||
|
if (formatter) {
|
||||||
|
const output = formatter.format(new Date(0));
|
||||||
|
yearSeparator = !!output.match(/\d,/);
|
||||||
|
return yearSeparator;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function isThisYear(date) {
|
||||||
|
const now = new Date();
|
||||||
|
return now.getUTCFullYear() === date.getUTCFullYear();
|
||||||
|
}
|
||||||
|
export function makeRelativeFormat(locale, options) {
|
||||||
|
if ('Intl' in window && 'RelativeTimeFormat' in window.Intl) {
|
||||||
|
try {
|
||||||
|
return new Intl.RelativeTimeFormat(locale, options);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (!(e instanceof RangeError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function localeFromElement(el) {
|
||||||
|
const container = el.closest('[lang]');
|
||||||
|
if (container instanceof HTMLElement && container.lang) {
|
||||||
|
return container.lang;
|
||||||
|
}
|
||||||
|
return 'default';
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user