Skip to content

Commit 1c86491

Browse files
committed
Add timestamp support
1 parent 6ea733f commit 1c86491

10 files changed

Lines changed: 114 additions & 23 deletions

File tree

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "YouTube Direct Messages",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"manifest_version": 3,
55
"description": "A Chrome extension to send direct messages on YouTube.",
66
"permissions": [

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "yt-dm-nl-extension",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"description": "",
55
"main": "index.js",
66
"scripts": {

src/types/videoDetails.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ interface VideoDetails {
44
thumbnail: string;
55
url: string;
66
duration: string;
7+
timestamp?: number;
78
}

src/ui/components/message.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { auth } from '../../firebase/firebase-config';
22
import { getUserProfile } from '../../firebase/firestore';
33
import { Message } from '../../types/message';
44
import { linkify } from '../utils/linkify';
5-
import { formatTime } from '../utils/time';
5+
import { formatSeconds, formatTime } from '../utils/time';
66

77
export async function createMessageElement(message: Message): Promise<HTMLElement> {
88
const user = auth.currentUser!;
@@ -21,17 +21,20 @@ export async function createMessageElement(message: Message): Promise<HTMLElemen
2121

2222
const messageContentWrapper = document.createElement('div');
2323
messageContentWrapper.className = 'yt-dm-message-content-wrapper';
24-
24+
2525
const bubble = document.createElement('div');
2626
bubble.className = 'yt-dm-message-bubble';
2727

2828
if (message.video) {
2929
bubble.classList.add('video-embed');
30-
30+
3131
const videoLink = document.createElement('a');
3232
if (message.video.type === 'short') {
3333
const videoId = message.video.url.split('/').pop();
3434
videoLink.href = `https://www.youtube.com/shorts/${videoId}`;
35+
} else if (message.video.timestamp) {
36+
const separator = videoLink.href.includes('?') ? '&' : '?';
37+
videoLink.href = message.video.url + `${separator}t=${message.video.timestamp}`;
3538
} else {
3639
videoLink.href = message.video.url;
3740
}
@@ -49,11 +52,19 @@ export async function createMessageElement(message: Message): Promise<HTMLElemen
4952
durationSpan.className = 'video-duration';
5053
durationSpan.textContent = message.video.duration;
5154
thumbnailContainer.append(thumbnailImg, durationSpan);
52-
55+
5356
const titleP = document.createElement('p');
5457
titleP.className = 'video-title';
5558
titleP.textContent = message.video.title;
5659

60+
if (message.video.timestamp) {
61+
const tsBadge = document.createElement('span');
62+
tsBadge.className = 'video-timestamp-badge';
63+
tsBadge.textContent = formatSeconds(message.video.timestamp);
64+
titleP.appendChild(tsBadge);
65+
}
66+
67+
5768
videoLink.append(thumbnailContainer, titleP);
5869
bubble.appendChild(videoLink);
5970
} else {
@@ -63,16 +74,16 @@ export async function createMessageElement(message: Message): Promise<HTMLElemen
6374
const contentFragment = linkify(message.text);
6475
messageText.appendChild(contentFragment);
6576
}
66-
77+
6778
bubble.appendChild(messageText);
6879
}
69-
80+
7081
const timestampSpan = document.createElement('span');
7182
timestampSpan.className = 'yt-dm-message-timestamp';
7283
timestampSpan.textContent = formatTime(message.timestamp);
7384

7485
messageContentWrapper.append(bubble, timestampSpan);
75-
86+
7687
container.appendChild(messageContentWrapper);
7788

7889
return container;

src/ui/panel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ function initialize() {
367367
sessionStorage.removeItem('yt-dm-pending-share');
368368
isSharingMode = true;
369369
pendingVideoData = JSON.parse(pendingShareJSON);
370+
console.log('Pending video data:', pendingVideoData);
370371
} else {
371372
isSharingMode = false;
372373
pendingVideoData = null;

src/ui/share-hook.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,19 +88,25 @@ function insertDMButton(container: Element | null) {
8888

8989
let videoId = parseVideoIdFromUrl(window.location.href);
9090

91-
if (!videoId) {
92-
const shareUrlInput = document.getElementById('share-url') as HTMLInputElement;
93-
if (shareUrlInput && shareUrlInput.value) {
94-
videoId = parseYouTubeVideoId(shareUrlInput.value);
95-
}
96-
}
91+
if (!videoId) {
92+
const shareUrlInput = document.getElementById('share-url') as HTMLInputElement;
93+
if (shareUrlInput && shareUrlInput.value) {
94+
videoId = parseYouTubeVideoId(shareUrlInput.value);
95+
}
96+
}
9797

9898
if (!videoId) {
9999
alert('Could not find a valid YouTube video ID on this page.');
100100
return;
101101
}
102102

103-
const videoData = await fetchYouTubeVideoDetails(videoId);
103+
const isChecked = document.querySelector('tp-yt-paper-ripple#ink')?.hasAttribute('checked');
104+
const timestamp = (document.querySelector('tp-yt-iron-input input.tp-yt-paper-input') as HTMLInputElement)?.value;
105+
const formattedTimestamp = timestamp.split(':').reduce((acc, time) => (60 * acc) + +time, 0);
106+
const videoData = isChecked && formattedTimestamp ?
107+
await fetchYouTubeVideoDetails(videoId, formattedTimestamp) :
108+
await fetchYouTubeVideoDetails(videoId);
109+
console.log('Video data fetched:', videoData);
104110

105111
sessionStorage.setItem('yt-dm-pending-share', JSON.stringify(videoData));
106112

src/ui/utils/time.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,10 @@ export function formatDateSeparator(timestamp: Timestamp | undefined): string {
3232
month: 'long',
3333
day: 'numeric'
3434
});
35+
}
36+
37+
export function formatSeconds(sec: number): string {
38+
const m = Math.floor(sec / 60);
39+
const s = sec % 60;
40+
return `${m}:${s.toString().padStart(2, '0')}`;
3541
}

src/ui/utils/youtube.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function formatSecondsToTime(secondsValue: number): string {
99
return `${minutes}:${paddedSeconds}`;
1010
}
1111

12-
export async function fetchYouTubeVideoDetails(videoId: string): Promise<VideoDetails> {
12+
export async function fetchYouTubeVideoDetails(videoId: string, timestamp?: number): Promise<VideoDetails> {
1313
const type = window.location.pathname.includes('/shorts/') ? 'short' : 'video';
1414

1515
let title = 'YouTube Video';
@@ -60,12 +60,22 @@ export async function fetchYouTubeVideoDetails(videoId: string): Promise<VideoDe
6060
}
6161
}
6262

63+
if (!timestamp)
64+
return {
65+
type: type,
66+
title: title,
67+
thumbnail: thumbnail,
68+
url: `https://youtu.be/${videoId}`,
69+
duration: durationString
70+
};
71+
6372
return {
6473
type: type,
6574
title: title,
6675
thumbnail: thumbnail,
6776
url: `https://youtu.be/${videoId}`,
6877
duration: durationString,
78+
timestamp: timestamp
6979
};
7080
}
7181

src/ui/views/chatView.ts

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,43 @@ export function renderChatView(
4444
shareButton.title = 'Share current video';
4545
shareButton.appendChild(createShareIcon());
4646

47+
const shareMenu = document.createElement('div');
48+
shareMenu.className = 'yt-dm-context-menu';
49+
50+
const shareWithTs = document.createElement('div');
51+
shareWithTs.className = 'yt-dm-context-menu-item';
52+
shareWithTs.textContent = 'Share with Timestamp';
53+
shareWithTs.addEventListener('click', async e => {
54+
e.stopPropagation();
55+
shareMenu.classList.remove('visible');
56+
await handleShareVideo(true);
57+
});
58+
shareMenu.appendChild(shareWithTs);
59+
60+
const shareWrapper = document.createElement('div');
61+
shareWrapper.className = 'yt-dm-header-controls';
62+
shareWrapper.append(shareButton);
63+
shareButton.appendChild(shareMenu);
64+
footerContainer.appendChild(shareWrapper);
65+
66+
const shareClickListener = async (e: MouseEvent) => {
67+
e.preventDefault();
68+
e.stopPropagation();
69+
shareMenu.classList.toggle('visible');
70+
71+
setTimeout(() => {
72+
const closeShareMenu = (evt: MouseEvent) => {
73+
if (!shareMenu.contains(evt.target as Node)) {
74+
shareMenu.classList.remove('visible');
75+
document.removeEventListener('click', closeShareMenu);
76+
}
77+
};
78+
document.addEventListener('click', closeShareMenu);
79+
}, 0);
80+
};
81+
82+
shareButton.addEventListener('contextmenu', shareClickListener);
83+
4784
const videoId = parseVideoIdFromUrl(window.location.href);
4885
if (!videoId) {
4986
footerContainer.style.paddingRight = '16px';
@@ -59,7 +96,7 @@ export function renderChatView(
5996
footerContainer.appendChild(inputWrapper);
6097
footerContainer.appendChild(shareButton);
6198

62-
const handleShareVideo = async () => {
99+
const handleShareVideo = async (includeTimestamp?: boolean | Event) => {
63100
if (!videoId) {
64101
alert("No video found on this page to share.");
65102
return;
@@ -70,8 +107,11 @@ export function renderChatView(
70107

71108
shareButton.disabled = true;
72109
try {
73-
const videoData = await fetchYouTubeVideoDetails(videoId);
74-
110+
const timestamp = parseInt((document.getElementById("movie_player") as any)?.getCurrentTime().toFixed(0));
111+
const videoData = (includeTimestamp === true && timestamp) ?? false ?
112+
await fetchYouTubeVideoDetails(videoId, timestamp) :
113+
await fetchYouTubeVideoDetails(videoId);
114+
75115
const optimisticMessage: Message = {
76116
id: `optimistic_${Date.now()}`,
77117
from: myUid,
@@ -127,7 +167,7 @@ export function renderChatView(
127167
id: `optimistic_${Date.now()}`,
128168
from: myUid,
129169
text: textToSend,
130-
timestamp: Timestamp.now()
170+
timestamp: Timestamp.now()
131171
};
132172

133173
messages = [...messages, optimisticMessage];
@@ -173,7 +213,7 @@ export function renderChatView(
173213

174214
allSeparators.forEach(separator => {
175215
const currentDateString = separator.getAttribute('data-date-string');
176-
216+
177217
if (currentDateString === lastDateString) {
178218
separator.remove();
179219
} else {
@@ -227,7 +267,7 @@ export function renderChatView(
227267
newMessagesListener = listenToNewMessages(chatId, latestMessage.id, async (newMsgs) => {
228268
const myUid = auth.currentUser?.uid;
229269
const trulyNewMsgs = newMsgs.filter(newMsg => newMsg.from !== myUid);
230-
270+
231271
if (trulyNewMsgs.length > 0) {
232272
const isAtBottom = listContainer.scrollHeight - listContainer.scrollTop - listContainer.clientHeight < 50;
233273
messages = [...messages, ...trulyNewMsgs];
@@ -247,5 +287,7 @@ export function renderChatView(
247287
inputElement.removeEventListener('input', handleInput);
248288
inputElement.removeEventListener('keydown', onKeydown);
249289
shareButton.removeEventListener('click', handleShareVideo);
290+
shareButton.removeEventListener('click', shareClickListener);
291+
shareButton.removeEventListener('contextmenu', shareClickListener);
250292
};
251293
}

styles/chat-ui.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@
403403
}
404404

405405
.share-video-button {
406+
position: relative;
406407
flex-shrink: 0;
407408
fill: #aaaaaa;
408409
margin-left: 4px;
@@ -517,6 +518,7 @@
517518
}
518519

519520
.yt-dm-context-menu {
521+
color: var(--yt-spec-text-primary);
520522
position: absolute;
521523
top: 40px;
522524
right: 0;
@@ -591,4 +593,16 @@
591593
flex-direction: column;
592594
flex-grow: 1;
593595
overflow: hidden;
596+
}
597+
598+
.video-timestamp-badge {
599+
display: inline-block;
600+
margin-left: 0.5em;
601+
padding: 0.1em 0.4em;
602+
font-size: 0.75em;
603+
font-weight: 500;
604+
background: rgba(0, 0, 0, 0.6);
605+
color: #fff;
606+
border-radius: 3px;
607+
vertical-align: middle;
594608
}

0 commit comments

Comments
 (0)