4
1
mirror of https://github.com/Qortal/qortal-ui.git synced 2025-02-12 02:05:51 +00:00
This commit is contained in:
AlphaX-Projects 2023-01-05 16:57:26 +01:00 committed by GitHub
parent abf736c640
commit 1a2a877a9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1415 additions and 0 deletions

View File

@ -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'
});

View 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 };

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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' });

View File

@ -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);
}

View File

@ -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);
}

View 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';
}