mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 05:37:59 +01:00
timeago: migrate to TypeScript (#226)
This commit is contained in:
parent
3cba72ec4c
commit
33a713310a
5 changed files with 146 additions and 15 deletions
114
assets/js/__tests__/timeago.spec.ts
Normal file
114
assets/js/__tests__/timeago.spec.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
import { timeAgo, setupTimestamps } from '../timeago';
|
||||
|
||||
const epochRfc3339 = '1970-01-01T00:00:00.000Z';
|
||||
|
||||
describe('Timeago functionality', () => {
|
||||
// TODO: is this robust? do we need e.g. timekeeper to freeze the time?
|
||||
function timeAgoWithSecondOffset(offset: number) {
|
||||
const utc = new Date(new Date().getTime() + offset * 1000).toISOString();
|
||||
|
||||
const timeEl = document.createElement('time');
|
||||
timeEl.setAttribute('datetime', utc);
|
||||
timeEl.textContent = utc;
|
||||
|
||||
timeAgo([timeEl]);
|
||||
return timeEl.textContent;
|
||||
}
|
||||
|
||||
/* eslint-disable no-implicit-coercion */
|
||||
it('should parse a time as less than a minute', () => {
|
||||
expect(timeAgoWithSecondOffset(-15)).toEqual('less than a minute ago');
|
||||
expect(timeAgoWithSecondOffset(+15)).toEqual('less than a minute from now');
|
||||
});
|
||||
|
||||
it('should parse a time as about a minute', () => {
|
||||
expect(timeAgoWithSecondOffset(-75)).toEqual('about a minute ago');
|
||||
expect(timeAgoWithSecondOffset(+75)).toEqual('about a minute from now');
|
||||
});
|
||||
|
||||
it('should parse a time as 30 minutes', () => {
|
||||
expect(timeAgoWithSecondOffset(-(60 * 30))).toEqual('30 minutes ago');
|
||||
expect(timeAgoWithSecondOffset(+(60 * 30))).toEqual('30 minutes from now');
|
||||
});
|
||||
|
||||
it('should parse a time as about an hour', () => {
|
||||
expect(timeAgoWithSecondOffset(-(60 * 60))).toEqual('about an hour ago');
|
||||
expect(timeAgoWithSecondOffset(+(60 * 60))).toEqual('about an hour from now');
|
||||
});
|
||||
|
||||
it('should parse a time as about 6 hours', () => {
|
||||
expect(timeAgoWithSecondOffset(-(60 * 60 * 6))).toEqual('about 6 hours ago');
|
||||
expect(timeAgoWithSecondOffset(+(60 * 60 * 6))).toEqual('about 6 hours from now');
|
||||
});
|
||||
|
||||
it('should parse a time as a day', () => {
|
||||
expect(timeAgoWithSecondOffset(-(60 * 60 * 36))).toEqual('a day ago');
|
||||
expect(timeAgoWithSecondOffset(+(60 * 60 * 36))).toEqual('a day from now');
|
||||
});
|
||||
|
||||
it('should parse a time as 25 days', () => {
|
||||
expect(timeAgoWithSecondOffset(-(60 * 60 * 24 * 25))).toEqual('25 days ago');
|
||||
expect(timeAgoWithSecondOffset(+(60 * 60 * 24 * 25))).toEqual('25 days from now');
|
||||
});
|
||||
|
||||
it('should parse a time as about a month', () => {
|
||||
expect(timeAgoWithSecondOffset(-(60 * 60 * 24 * 35))).toEqual('about a month ago');
|
||||
expect(timeAgoWithSecondOffset(+(60 * 60 * 24 * 35))).toEqual('about a month from now');
|
||||
});
|
||||
|
||||
it('should parse a time as 3 months', () => {
|
||||
expect(timeAgoWithSecondOffset(-(60 * 60 * 24 * 30 * 3))).toEqual('3 months ago');
|
||||
expect(timeAgoWithSecondOffset(+(60 * 60 * 24 * 30 * 3))).toEqual('3 months from now');
|
||||
});
|
||||
|
||||
it('should parse a time as about a year', () => {
|
||||
expect(timeAgoWithSecondOffset(-(60 * 60 * 24 * 30 * 13))).toEqual('about a year ago');
|
||||
expect(timeAgoWithSecondOffset(+(60 * 60 * 24 * 30 * 13))).toEqual('about a year from now');
|
||||
});
|
||||
|
||||
it('should parse a time as 5 years', () => {
|
||||
expect(timeAgoWithSecondOffset(-(60 * 60 * 24 * 30 * 12 * 5))).toEqual('5 years ago');
|
||||
expect(timeAgoWithSecondOffset(+(60 * 60 * 24 * 30 * 12 * 5))).toEqual('5 years from now');
|
||||
});
|
||||
/* eslint-enable no-implicit-coercion */
|
||||
|
||||
it('should ignore time elements without a datetime attribute', () => {
|
||||
const timeEl = document.createElement('time');
|
||||
const value = Math.random().toString();
|
||||
|
||||
timeEl.textContent = value;
|
||||
timeAgo([timeEl]);
|
||||
|
||||
expect(timeEl.textContent).toEqual(value);
|
||||
});
|
||||
|
||||
it('should not reset title attribute if it already exists', () => {
|
||||
const timeEl = document.createElement('time');
|
||||
const value = Math.random().toString();
|
||||
|
||||
timeEl.setAttribute('datetime', epochRfc3339);
|
||||
timeEl.setAttribute('title', value);
|
||||
timeAgo([timeEl]);
|
||||
|
||||
expect(timeEl.getAttribute('title')).toEqual(value);
|
||||
expect(timeEl.textContent).not.toEqual(epochRfc3339);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Automatic timestamps', () => {
|
||||
it('should process all timestamps in the document', () => {
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
const timeEl = document.createElement('time');
|
||||
timeEl.setAttribute('datetime', epochRfc3339);
|
||||
timeEl.textContent = epochRfc3339;
|
||||
|
||||
document.documentElement.insertAdjacentElement('beforeend', timeEl);
|
||||
}
|
||||
|
||||
setupTimestamps();
|
||||
|
||||
for (const timeEl of document.getElementsByTagName('time')) {
|
||||
expect(timeEl.textContent).not.toEqual(epochRfc3339);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -6,6 +6,7 @@ import { $ } from './utils/dom';
|
|||
import { showOwnedComments } from './communications/comment';
|
||||
import { filterNode } from './imagesclientside';
|
||||
import { fetchHtml } from './utils/requests';
|
||||
import { timeAgo } from './timeago';
|
||||
|
||||
function handleError(response) {
|
||||
|
||||
|
@ -91,7 +92,7 @@ function insertParentPost(data, clickedLink, fullComment) {
|
|||
fullComment.previousSibling.classList.add('fetched-comment');
|
||||
|
||||
// Execute timeago on the new comment - it was not present when first run
|
||||
window.booru.timeAgo(fullComment.previousSibling.getElementsByTagName('time'));
|
||||
timeAgo(fullComment.previousSibling.getElementsByTagName('time'));
|
||||
|
||||
// Add class active_reply_link to the clicked link
|
||||
clickedLink.classList.add('active_reply_link');
|
||||
|
@ -125,7 +126,7 @@ function displayComments(container, commentsHtml) {
|
|||
container.innerHTML = commentsHtml;
|
||||
|
||||
// Execute timeago on comments
|
||||
window.booru.timeAgo(document.getElementsByTagName('time'));
|
||||
timeAgo(document.getElementsByTagName('time'));
|
||||
|
||||
// Filter images in the comments
|
||||
filterNode(container);
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
* Frontend timestamps.
|
||||
*/
|
||||
|
||||
const strings = {
|
||||
import { assertNotNull } from './utils/assert';
|
||||
|
||||
const strings: Record<string, string> = {
|
||||
seconds: 'less than a minute',
|
||||
minute: 'about a minute',
|
||||
minutes: '%d minutes',
|
||||
|
@ -16,16 +18,21 @@ const strings = {
|
|||
years: '%d years',
|
||||
};
|
||||
|
||||
function distance(time) {
|
||||
return new Date() - time;
|
||||
function distance(time: Date) {
|
||||
return new Date().getTime() - time.getTime();
|
||||
}
|
||||
|
||||
function substitute(key, amount) {
|
||||
return strings[key].replace('%d', Math.round(amount));
|
||||
function substitute(key: string, amount: number) {
|
||||
return strings[key].replace('%d', Math.round(amount).toString());
|
||||
}
|
||||
|
||||
function setTimeAgo(el) {
|
||||
const date = new Date(el.getAttribute('datetime'));
|
||||
function setTimeAgo(el: HTMLTimeElement) {
|
||||
const datetime = el.getAttribute('datetime');
|
||||
if (!datetime) {
|
||||
return;
|
||||
}
|
||||
|
||||
const date = new Date(datetime);
|
||||
const distMillis = distance(date);
|
||||
|
||||
const seconds = Math.abs(distMillis) / 1000,
|
||||
|
@ -49,20 +56,20 @@ function setTimeAgo(el) {
|
|||
substitute('years', years);
|
||||
|
||||
if (!el.getAttribute('title')) {
|
||||
el.setAttribute('title', el.textContent);
|
||||
el.setAttribute('title', assertNotNull(el.textContent));
|
||||
}
|
||||
el.textContent = words + (distMillis < 0 ? ' from now' : ' ago');
|
||||
}
|
||||
|
||||
function timeAgo(args) {
|
||||
[].forEach.call(args, el => setTimeAgo(el));
|
||||
export function timeAgo(args: HTMLTimeElement[] | HTMLCollectionOf<HTMLTimeElement>) {
|
||||
for (const el of args) {
|
||||
setTimeAgo(el);
|
||||
}
|
||||
}
|
||||
|
||||
function setupTimestamps() {
|
||||
export function setupTimestamps() {
|
||||
timeAgo(document.getElementsByTagName('time'));
|
||||
window.setTimeout(setupTimestamps, 60000);
|
||||
}
|
||||
|
||||
export { setupTimestamps };
|
||||
|
||||
window.booru.timeAgo = timeAgo;
|
|
@ -2,6 +2,8 @@ import '@testing-library/jest-dom';
|
|||
import { matchNone } from '../js/query/boolean';
|
||||
|
||||
window.booru = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
timeAgo: () => {},
|
||||
csrfToken: 'mockCsrfToken',
|
||||
hiddenTag: '/mock-tagblocked.svg',
|
||||
hiddenTagList: [],
|
||||
|
|
7
assets/types/booru-object.d.ts
vendored
7
assets/types/booru-object.d.ts
vendored
|
@ -13,6 +13,13 @@ interface Interaction {
|
|||
}
|
||||
|
||||
interface BooruObject {
|
||||
/**
|
||||
* Automatic timestamp recalculation function for userscript use
|
||||
*/
|
||||
timeAgo: (args: HTMLTimeElement[]) => void;
|
||||
/**
|
||||
* Anti-forgery token sent by the server
|
||||
*/
|
||||
csrfToken: string;
|
||||
/**
|
||||
* One of the specified values, based on user setting
|
||||
|
|
Loading…
Reference in a new issue