From 7aa083e1faf7b7956eb8f2d14546321a74ff1d5c Mon Sep 17 00:00:00 2001 From: Nufflee Date: Sat, 20 Jul 2019 01:23:09 +0200 Subject: [PATCH 1/5] Add event support to Tag. --- ts/html/Tag.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ts/html/Tag.ts b/ts/html/Tag.ts index 4176f98..fd7a8fc 100644 --- a/ts/html/Tag.ts +++ b/ts/html/Tag.ts @@ -3,7 +3,8 @@ import UiComponent from '../UiComponent'; export default class Tag implements UiComponent { constructor(private _name: string, private _body?: UiComponent, - private _attrs?: {[key: string]: any}) { + private _attrs?: {[key: string]: any}, + private _events?: {[key: string]: EventListenerOrEventListenerObject}) { } appendTo(entry: HTMLElement | null): void { @@ -21,6 +22,12 @@ export default class Tag implements UiComponent { } } + if (this._events) { + for (let eventKey in this._events) { + element.addEventListener(eventKey, this._events[eventKey]) + } + } + if (this._body) { this._body.appendTo(element); } From 5dd32744eb780b5af100db93f74f0d01026c5e71 Mon Sep 17 00:00:00 2001 From: Nufflee Date: Sat, 20 Jul 2019 01:44:39 +0200 Subject: [PATCH 2/5] Add a button to copy event id/hash. --- package.json | 1 + scss/main.scss | 6 ++++++ ts/CancelledEvent.ts | 12 ++++-------- ts/CurrentEvent.ts | 13 +++++-------- ts/Event.ts | 14 ++++++++------ ts/FutureEvent.ts | 12 ++++-------- ts/PastEvent.ts | 12 ++++-------- ts/Timestamp.ts | 35 +++++++++++++++++++++++++++++++++++ 8 files changed, 67 insertions(+), 38 deletions(-) create mode 100644 ts/Timestamp.ts diff --git a/package.json b/package.json index c231f8c..5f65b6a 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/moment": "^2.13.0", "@types/moment-timezone": "^0.5.6", "browserify": "^16.2.2", + "clipboard-copy": "^3.0.0", "markdown-it": "^8.4.2", "moment": "^2.22.2", "moment-timezone": "^0.5.20", diff --git a/scss/main.scss b/scss/main.scss index 0110dbd..7a32749 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -77,6 +77,12 @@ body { color: $red; } + .copy { + color: $dimmed_foreground; + cursor: pointer; + margin-left: 5px; + } + .description { margin-top: 20px; text-align: left; diff --git a/ts/CancelledEvent.ts b/ts/CancelledEvent.ts index 81fa536..90862ce 100644 --- a/ts/CancelledEvent.ts +++ b/ts/CancelledEvent.ts @@ -4,21 +4,17 @@ import * as moment from 'moment'; import ComponentsArray from './ComponentsArray'; import Countdown from './Countdown'; import UiComponent from './UiComponent'; +import Timestamp from './Timestamp'; export default class CancelledEvent implements UiComponent { - constructor(private _event: dto.Event) { + constructor(private _event: dto.Event, + private _timestamp: string) { } appendTo(entry: HTMLElement | null): void { new html.Div( new ComponentsArray([ - new html.Div( - new html.Href( - `#_${this._event.datetime.utc().unix()}`, - new html.Text(`${this._event.datetime.utc().unix()}`) - ), - {"class": "timestamp"} - ), + new Timestamp(this._timestamp), new html.H1( new html.Href( `${this._event.url}`, diff --git a/ts/CurrentEvent.ts b/ts/CurrentEvent.ts index 4bab432..13e1e6b 100644 --- a/ts/CurrentEvent.ts +++ b/ts/CurrentEvent.ts @@ -1,25 +1,22 @@ import * as dto from './dto'; import * as html from './html'; import * as moment from 'moment'; +import copy = require('clipboard-copy') import ComponentsArray from './ComponentsArray'; import Countdown from './Countdown'; import UiComponent from './UiComponent'; import TwitchPlayer from './TwitchPlayer'; +import Timestamp from './Timestamp' export default class CurrentEvent implements UiComponent { - constructor(private _event: dto.Event) { + constructor(private _event: dto.Event, + private _timestamp: string) { } appendTo(entry: HTMLElement | null): void { new html.Div( new ComponentsArray([ - new html.Div( - new html.Href( - `#_${this._event.datetime.utc().unix()}`, - new html.Text(`${this._event.datetime.utc().unix()}`) - ), - {"class": "timestamp"} - ), + new Timestamp(this._timestamp), new html.Div( new html.Href( this._event.channel ? this._event.channel : "https://twitch.tv/tsoding", diff --git a/ts/Event.ts b/ts/Event.ts index 4195fe3..1abee4b 100644 --- a/ts/Event.ts +++ b/ts/Event.ts @@ -13,19 +13,21 @@ export default class Event implements UiComponent { } appendTo(entry: HTMLElement | null): void { - let secondsDiff = moment().diff(this._event.datetime, 'seconds'); + const secondsDiff = moment().diff(this._event.datetime, 'seconds'); + const timestamp = this._event.datetime.utc().unix().toString(); if (this.isCancelled()) { - new CancelledEvent(this._event).appendTo(entry); + new CancelledEvent(this._event, timestamp).appendTo(entry); } else if (0 <= secondsDiff && secondsDiff < 4 * 60 * 60) { - new CurrentEvent(this._event).appendTo(entry); + new CurrentEvent(this._event, timestamp).appendTo(entry); } else if (secondsDiff >= 4 * 60 * 60) { - new PastEvent(this._event).appendTo(entry); + new PastEvent(this._event, timestamp).appendTo(entry); } else { - new FutureEvent(this._event).appendTo(entry); + new FutureEvent(this._event, timestamp).appendTo(entry); } - const hashId = "#_" + this._event.datetime.utc().unix(); + const hashId = "#_" + timestamp; + if (window.location.hash == hashId) { window.location.hash = ""; setTimeout(() => { window.location.hash = hashId; }, 0); diff --git a/ts/FutureEvent.ts b/ts/FutureEvent.ts index e72cb20..e085a7c 100644 --- a/ts/FutureEvent.ts +++ b/ts/FutureEvent.ts @@ -4,21 +4,17 @@ import * as moment from 'moment'; import ComponentsArray from './ComponentsArray'; import Countdown from './Countdown'; import UiComponent from './UiComponent'; +import Timestamp from './Timestamp'; export default class FutureEvent implements UiComponent { - constructor(private _event: dto.Event) { + constructor(private _event: dto.Event, + private _timestamp: string) { } appendTo(entry: HTMLElement | null): void { new html.Div( new ComponentsArray([ - new html.Div( - new html.Href( - `#_${this._event.datetime.utc().unix()}`, - new html.Text(`${this._event.datetime.utc().unix()}`) - ), - {"class": "timestamp"} - ), + new Timestamp(this._timestamp), new html.H1( new html.Href( `${this._event.url}`, diff --git a/ts/PastEvent.ts b/ts/PastEvent.ts index 9eb2195..c488923 100644 --- a/ts/PastEvent.ts +++ b/ts/PastEvent.ts @@ -4,21 +4,17 @@ import * as moment from 'moment'; import ComponentsArray from './ComponentsArray'; import Countdown from './Countdown'; import UiComponent from './UiComponent'; +import Timestamp from './Timestamp'; export default class PastEvent implements UiComponent { - constructor(private _event: dto.Event) { + constructor(private _event: dto.Event, + private _timestamp: string) { } appendTo(entry: HTMLElement | null): void { new html.Div( new ComponentsArray([ - new html.Div( - new html.Href( - `#_${this._event.datetime.utc().unix()}`, - new html.Text(`${this._event.datetime.utc().unix()}`) - ), - {"class": "timestamp"} - ), + new Timestamp(this._timestamp), new html.H1( new html.Href( `${this._event.url}`, diff --git a/ts/Timestamp.ts b/ts/Timestamp.ts new file mode 100644 index 0000000..6a9c16e --- /dev/null +++ b/ts/Timestamp.ts @@ -0,0 +1,35 @@ +import * as dto from './dto'; +import * as html from './html'; +import * as moment from 'moment'; +import copy = require('clipboard-copy') +import ComponentsArray from './ComponentsArray'; +import Countdown from './Countdown'; +import UiComponent from './UiComponent'; +import TwitchPlayer from './TwitchPlayer'; + +export default class CurrentEvent implements UiComponent { + constructor(private _timestamp: string) { + } + + onCopyClick = () => { + copy(this._timestamp) + } + + appendTo(entry: HTMLElement | null): void { + new html.Div( + new ComponentsArray([ + new html.Href( + `#_${this._timestamp}`, + new html.Text(`${this._timestamp}`) + ), + new html.Tag( + "i", + new html.Empty(), + {"class": "copy fas fa-copy fa-lg"}, + {"click": this.onCopyClick} + ) + ]), + {"class": "timestamp"} + ).appendTo(entry); + } +} From f81adc7297cd2c4b5da3723e3373c92480e3a627 Mon Sep 17 00:00:00 2001 From: Nufflee Date: Sat, 20 Jul 2019 02:51:49 +0200 Subject: [PATCH 3/5] Use our own implementation of copyToClipboard. --- package.json | 1 - ts/CurrentEvent.ts | 1 - ts/Timestamp.ts | 4 ++-- ts/util/copyToClipboard.ts | 48 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 ts/util/copyToClipboard.ts diff --git a/package.json b/package.json index 5f65b6a..c231f8c 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "@types/moment": "^2.13.0", "@types/moment-timezone": "^0.5.6", "browserify": "^16.2.2", - "clipboard-copy": "^3.0.0", "markdown-it": "^8.4.2", "moment": "^2.22.2", "moment-timezone": "^0.5.20", diff --git a/ts/CurrentEvent.ts b/ts/CurrentEvent.ts index 13e1e6b..97371cd 100644 --- a/ts/CurrentEvent.ts +++ b/ts/CurrentEvent.ts @@ -1,7 +1,6 @@ import * as dto from './dto'; import * as html from './html'; import * as moment from 'moment'; -import copy = require('clipboard-copy') import ComponentsArray from './ComponentsArray'; import Countdown from './Countdown'; import UiComponent from './UiComponent'; diff --git a/ts/Timestamp.ts b/ts/Timestamp.ts index 6a9c16e..69bc6db 100644 --- a/ts/Timestamp.ts +++ b/ts/Timestamp.ts @@ -1,18 +1,18 @@ import * as dto from './dto'; import * as html from './html'; import * as moment from 'moment'; -import copy = require('clipboard-copy') import ComponentsArray from './ComponentsArray'; import Countdown from './Countdown'; import UiComponent from './UiComponent'; import TwitchPlayer from './TwitchPlayer'; +import copyToClipboard from './util/copyToClipboard'; export default class CurrentEvent implements UiComponent { constructor(private _timestamp: string) { } onCopyClick = () => { - copy(this._timestamp) + copyToClipboard(this._timestamp) } appendTo(entry: HTMLElement | null): void { diff --git a/ts/util/copyToClipboard.ts b/ts/util/copyToClipboard.ts new file mode 100644 index 0000000..ef1a996 --- /dev/null +++ b/ts/util/copyToClipboard.ts @@ -0,0 +1,48 @@ +export default function copyToClipboard(text: string) { + // Use the Async Clipboard API when available. Requires a secure browing context (i.e. HTTPS) + if ((navigator as any).clipboard) { + return (navigator as any).clipboard.writeText(text) + } + + // ...Otherwise, use document.execCommand() fallback + + // Put the text to copy into a + const span = document.createElement('span') + span.textContent = text + + // Preserve consecutive spaces and newlines + span.style.whiteSpace = 'pre' + + // Add the to the page + document.body.appendChild(span) + + // Make a selection object representing the range of text selected by the user + const selection = window.getSelection() + const range = window.document.createRange() + + if (!selection) { + return Promise.reject() + } + + selection.removeAllRanges() + range.selectNode(span) + selection.addRange(range) + + // Copy text to the clipboard + let success = false + try { + success = window.document.execCommand('copy') + } catch (err) { + console.log('Failed to copy text. Error: ', err) + } + + // Cleanup + selection.removeAllRanges() + window.document.body.removeChild(span) + + // The Async Clipboard API returns a promise that may reject with `undefined` + // so we match that here for consistency. + return success + ? Promise.resolve() + : Promise.reject() +} \ No newline at end of file From 9681e660c3cac6ba0bd612ac43e9d8af6d15238d Mon Sep 17 00:00:00 2001 From: Nufflee Date: Mon, 10 Feb 2020 18:23:43 +0100 Subject: [PATCH 4/5] Add .vscode/ to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1c6c3a3..ddcf26e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ dist/ -ts/**/*.js \ No newline at end of file +ts/**/*.js +.vscode/ \ No newline at end of file From aa29eb7749829b9207f943c1561527f8f6924b02 Mon Sep 17 00:00:00 2001 From: Nufflee Date: Mon, 10 Feb 2020 20:08:35 +0100 Subject: [PATCH 5/5] Add timestamp() function to dto.Event. timestamp() function is used to retrieve the UNIX timestamp of a given event. --- ts/CancelledEvent.ts | 5 ++--- ts/CurrentEvent.ts | 5 ++--- ts/Event.ts | 11 +++++------ ts/EventsForDay.ts | 4 ++-- ts/FutureEvent.ts | 5 ++--- ts/PastEvent.ts | 5 ++--- ts/Timestamp.ts | 2 +- ts/dto/Event.ts | 17 +++++++++++------ ts/dto/PatchedEvent.ts | 14 ++------------ ts/util/copyToClipboard.ts | 7 ++++--- 10 files changed, 33 insertions(+), 42 deletions(-) diff --git a/ts/CancelledEvent.ts b/ts/CancelledEvent.ts index 90862ce..e284cec 100644 --- a/ts/CancelledEvent.ts +++ b/ts/CancelledEvent.ts @@ -7,14 +7,13 @@ import UiComponent from './UiComponent'; import Timestamp from './Timestamp'; export default class CancelledEvent implements UiComponent { - constructor(private _event: dto.Event, - private _timestamp: string) { + constructor(private _event: dto.Event) { } appendTo(entry: HTMLElement | null): void { new html.Div( new ComponentsArray([ - new Timestamp(this._timestamp), + new Timestamp(this._event.timestamp()), new html.H1( new html.Href( `${this._event.url}`, diff --git a/ts/CurrentEvent.ts b/ts/CurrentEvent.ts index 97371cd..5d714ba 100644 --- a/ts/CurrentEvent.ts +++ b/ts/CurrentEvent.ts @@ -8,14 +8,13 @@ import TwitchPlayer from './TwitchPlayer'; import Timestamp from './Timestamp' export default class CurrentEvent implements UiComponent { - constructor(private _event: dto.Event, - private _timestamp: string) { + constructor(private _event: dto.Event) { } appendTo(entry: HTMLElement | null): void { new html.Div( new ComponentsArray([ - new Timestamp(this._timestamp), + new Timestamp(this._event.timestamp()), new html.Div( new html.Href( this._event.channel ? this._event.channel : "https://twitch.tv/tsoding", diff --git a/ts/Event.ts b/ts/Event.ts index 1abee4b..3550b3b 100644 --- a/ts/Event.ts +++ b/ts/Event.ts @@ -14,19 +14,18 @@ export default class Event implements UiComponent { appendTo(entry: HTMLElement | null): void { const secondsDiff = moment().diff(this._event.datetime, 'seconds'); - const timestamp = this._event.datetime.utc().unix().toString(); if (this.isCancelled()) { - new CancelledEvent(this._event, timestamp).appendTo(entry); + new CancelledEvent(this._event).appendTo(entry); } else if (0 <= secondsDiff && secondsDiff < 4 * 60 * 60) { - new CurrentEvent(this._event, timestamp).appendTo(entry); + new CurrentEvent(this._event).appendTo(entry); } else if (secondsDiff >= 4 * 60 * 60) { - new PastEvent(this._event, timestamp).appendTo(entry); + new PastEvent(this._event).appendTo(entry); } else { - new FutureEvent(this._event, timestamp).appendTo(entry); + new FutureEvent(this._event).appendTo(entry); } - const hashId = "#_" + timestamp; + const hashId = "#_" + this._event.timestamp(); if (window.location.hash == hashId) { window.location.hash = ""; diff --git a/ts/EventsForDay.ts b/ts/EventsForDay.ts index d537091..9e8645a 100644 --- a/ts/EventsForDay.ts +++ b/ts/EventsForDay.ts @@ -67,8 +67,8 @@ export default class EventsForDay implements UiComponent { .map( (e) => new Event( this._state.eventPatches - ? new dto.PatchedEvent(e, this._state.eventPatches[e.datetime.utc().unix()]) - : e, + ? new dto.PatchedEvent(e as dto.Event, this._state.eventPatches[e.datetime.utc().unix()]) + : e as dto.Event, this._state.cancelledEvents ) ) diff --git a/ts/FutureEvent.ts b/ts/FutureEvent.ts index e085a7c..b467c80 100644 --- a/ts/FutureEvent.ts +++ b/ts/FutureEvent.ts @@ -7,14 +7,13 @@ import UiComponent from './UiComponent'; import Timestamp from './Timestamp'; export default class FutureEvent implements UiComponent { - constructor(private _event: dto.Event, - private _timestamp: string) { + constructor(private _event: dto.Event) { } appendTo(entry: HTMLElement | null): void { new html.Div( new ComponentsArray([ - new Timestamp(this._timestamp), + new Timestamp(this._event.timestamp()), new html.H1( new html.Href( `${this._event.url}`, diff --git a/ts/PastEvent.ts b/ts/PastEvent.ts index c488923..bc917a6 100644 --- a/ts/PastEvent.ts +++ b/ts/PastEvent.ts @@ -7,14 +7,13 @@ import UiComponent from './UiComponent'; import Timestamp from './Timestamp'; export default class PastEvent implements UiComponent { - constructor(private _event: dto.Event, - private _timestamp: string) { + constructor(private _event: dto.Event) { } appendTo(entry: HTMLElement | null): void { new html.Div( new ComponentsArray([ - new Timestamp(this._timestamp), + new Timestamp(this._event.timestamp()), new html.H1( new html.Href( `${this._event.url}`, diff --git a/ts/Timestamp.ts b/ts/Timestamp.ts index 69bc6db..e14351c 100644 --- a/ts/Timestamp.ts +++ b/ts/Timestamp.ts @@ -7,7 +7,7 @@ import UiComponent from './UiComponent'; import TwitchPlayer from './TwitchPlayer'; import copyToClipboard from './util/copyToClipboard'; -export default class CurrentEvent implements UiComponent { +export default class Timestamp implements UiComponent { constructor(private _timestamp: string) { } diff --git a/ts/dto/Event.ts b/ts/dto/Event.ts index aa7a161..2ed9482 100644 --- a/ts/dto/Event.ts +++ b/ts/dto/Event.ts @@ -1,9 +1,14 @@ import * as moment from 'moment'; -export default interface Event { - datetime: moment.Moment, - title: string, - description: string, - url: string, - channel: string, +export default class Event { + constructor(public datetime: moment.Moment, + public title: string, + public description: string, + public url: string, + public channel: string) { + } + + timestamp() { + return this.datetime.utc().unix().toString(); + } } diff --git a/ts/dto/PatchedEvent.ts b/ts/dto/PatchedEvent.ts index e4fa0cb..a26233e 100644 --- a/ts/dto/PatchedEvent.ts +++ b/ts/dto/PatchedEvent.ts @@ -2,19 +2,9 @@ import * as moment from 'moment'; import Event from './Event'; import EventPatch from './EventPatch'; -export default class PatchedEvent implements Event { - datetime: moment.Moment; - title: string; - description: string; - url: string; - channel: string; - +export default class PatchedEvent extends Event { constructor(event: Event, eventPatch: EventPatch | undefined) { - this.datetime = event.datetime; - this.title = event.title; - this.description = event.description; - this.url = event.url; - this.channel = event.channel; + super(event.datetime, event.title, event.description, event.url, event.channel); if (eventPatch) { if (eventPatch.title) { diff --git a/ts/util/copyToClipboard.ts b/ts/util/copyToClipboard.ts index ef1a996..78ef2ae 100644 --- a/ts/util/copyToClipboard.ts +++ b/ts/util/copyToClipboard.ts @@ -1,3 +1,5 @@ +// Adapted from https://github.com/feross/clipboard-copy + export default function copyToClipboard(text: string) { // Use the Async Clipboard API when available. Requires a secure browing context (i.e. HTTPS) if ((navigator as any).clipboard) { @@ -19,7 +21,7 @@ export default function copyToClipboard(text: string) { // Make a selection object representing the range of text selected by the user const selection = window.getSelection() const range = window.document.createRange() - + if (!selection) { return Promise.reject() } @@ -40,8 +42,7 @@ export default function copyToClipboard(text: string) { selection.removeAllRanges() window.document.body.removeChild(span) - // The Async Clipboard API returns a promise that may reject with `undefined` - // so we match that here for consistency. + // The Async Clipboard API returns a promise that may reject with `undefined` so we match that here for consistency. return success ? Promise.resolve() : Promise.reject()