11---
22import comments from ' @/data/comments.json' ;
33import siteConfig from ' @/site.config.ts' ;
4+ import BaseHead from ' @/components/BaseHead.astro' ;
5+ import { Image } from ' astro:assets' ;
46
57// Define types for comments and reaction groups
68interface ReactionGroup {
@@ -21,11 +23,21 @@ interface Comment {
2123 };
2224 bodyHTML: string ;
2325 reactionGroups? : ReactionGroup []; // Optional as it might not always be present
26+ parentId: string | null ;
27+ replyToId: string | null ;
28+ replyToAuthor: string | null ;
29+ level: number ;
30+ type: string ;
31+ discussionId: string ;
32+ isDiscussion: boolean ;
33+ title: string ;
2434}
2535
2636const title = ' Guestbook' ;
2737const description = ' A collection of all comments, styled like VS Code.' ;
2838
39+ const commentsById = new Map (comments .map ((c : Comment ) => [c .id , c ]));
40+
2941const discussionsUrl = ` https://github.com/${siteConfig .integ .giscus ?.repo }/discussions ` ;
3042
3143// Helper function to format time ago
@@ -70,11 +82,7 @@ function getReactionEmoji(reaction: string) {
7082<!DOCTYPE html >
7183<html lang =" zh-CN" >
7284 <head >
73- <meta charset =" UTF-8" />
74- <meta name =" viewport" content =" width=device-width, initial-scale=1.0" />
75- <title >{ title } </title >
76- <meta name =" description" content ={ description } />
77- <meta property =" og:title" content ={ title } />
85+ <BaseHead title ={ title } description ={ description } ogImage =' /images/guest-book.png' />
7886 <style >
7987 :root {
8088 --accent-color: #0969da;
@@ -117,10 +125,36 @@ function getReactionEmoji(reaction: string) {
117125 border-right: 1px solid var(--border-color);
118126 display: flex;
119127 flex-direction: column;
128+ -ms-overflow-style: none; /* IE and Edge */
129+ scrollbar-width: none; /* Firefox */
130+ }
131+
132+ .comments-pane::-webkit-scrollbar {
133+ display: none; /* Chrome, Safari, Opera*/
120134 }
121135
122136 .giscus-pane {
123137 width: 40%;
138+ display: flex;
139+ flex-direction: column;
140+ overflow-y: auto;
141+ }
142+
143+ .giscus-image-container {
144+ display: flex;
145+ justify-content: center;
146+ align-items: center;
147+ margin-bottom: 1.5rem; /* Space between image and Giscus */
148+ }
149+
150+ .giscus-image-container img {
151+ width: 100%;
152+ height: 100%;
153+ object-fit: cover;
154+ }
155+
156+ .giscus-comments-container {
157+ flex-grow: 1; /* Giscus takes remaining space */
124158 padding: 1.5rem;
125159 }
126160
@@ -200,6 +234,10 @@ function getReactionEmoji(reaction: string) {
200234 opacity: 1;
201235 }
202236
237+ .comment-item.highlighted-comment {
238+ background: #fffacd; /* A more noticeable light yellow for highlighting */
239+ }
240+
203241 .line-number {
204242 width: 50px;
205243 color: var(--text-secondary-color);
@@ -307,6 +345,49 @@ function getReactionEmoji(reaction: string) {
307345 border-color: var(--accent-color);
308346 }
309347
348+ .quoted-reply {
349+ border-left: 3px solid var(--border-color);
350+ padding-left: 12px;
351+ margin-bottom: 8px;
352+ font-size: 13px;
353+ color: var(--text-secondary-color);
354+ }
355+ .quoted-reply p {
356+ margin-bottom: 4px;
357+ }
358+ .quoted-reply a {
359+ color: var(--accent-color);
360+ text-decoration: none;
361+ }
362+ .quoted-reply a:hover {
363+ text-decoration: underline;
364+ }
365+ .quoted-reply blockquote {
366+ margin: 0;
367+ padding: 0;
368+ border: none;
369+ max-height: 7em; /* Limit height of quoted text */
370+ overflow: hidden;
371+ text-overflow: ellipsis;
372+ opacity: 0.8;
373+ position: relative;
374+ }
375+ .quoted-reply blockquote::after {
376+ content: '';
377+ position: absolute;
378+ bottom: 0;
379+ left: 0;
380+ right: 0;
381+ height: 1.5em;
382+ background: linear-gradient(to bottom, transparent, var(--bg-color));
383+ }
384+ .quoted-reply blockquote > *:first-child {
385+ margin-top: 0;
386+ }
387+ .quoted-reply blockquote > *:last-child {
388+ margin-bottom: 0;
389+ }
390+
310391 /* Responsive */
311392 @media (max-width: 768px) {
312393 .guestbook-container {
@@ -366,8 +447,10 @@ function getReactionEmoji(reaction: string) {
366447 </div >
367448 <div class =" comments-list" >
368449 {
369- comments .map ((comment : Comment , index : number ) => (
370- <div class = " comment-item" >
450+ comments .map ((comment : Comment , index : number ) => {
451+ const repliedToComment = comment .replyToId ? commentsById .get (comment .replyToId ) : null ;
452+ return (
453+ <div class = " comment-item" id = { comment .id } >
371454 <div class = " line-number" >{ comments .length - index } </div >
372455 <div class = " comment-content-wrapper" >
373456 <div class = " comment-header" >
@@ -387,6 +470,14 @@ function getReactionEmoji(reaction: string) {
387470 rel = " noopener noreferrer" >View on GitHub</a
388471 >
389472 </div >
473+ { repliedToComment && (
474+ <div class = " quoted-reply" >
475+ <p >
476+ Replying to <a href = { ` #${repliedToComment .id } ` } >@{ comment .replyToAuthor } </a >
477+ </p >
478+ <blockquote set :html = { repliedToComment .bodyHTML } />
479+ </div >
480+ )}
390481 <div class = " comment-body" set :html = { comment .bodyHTML } />
391482 <div class = " reactions" >
392483 { comment .reactionGroups &&
@@ -401,37 +492,84 @@ function getReactionEmoji(reaction: string) {
401492 </div >
402493 </div >
403494 </div >
404- ))
495+ )} )
405496 }
406497 </div >
407498 <div class = " see-more-container" >
408499 <a href = { discussionsUrl } class = " see-more-btn" target = " _blank" rel = " noopener noreferrer" >
409- See more on GitHub
500+ See more on GitHub
410501 </a >
411502 </div >
412503 < / div >
413504
414505 < ! -- Right Pane : Giscus -->
415506 <div class = " giscus-pane" >
416- <script
417- is :inline
418- src = " https://giscus.app/client.js"
419- data-repo = " CatCodeMe/catcodeme.github.io"
420- data-repo-id = " R_kgDOLTuIuQ"
421- data-category = " General"
422- data-category-id = " DIC_kwDOLTuIuc4Csz7O"
423- data-mapping = " og:title"
424- data-strict = " 0"
425- data-reactions-enabled = " 1"
426- data-emit-metadata = " 0"
427- data-input-position = " top"
428- data-theme = " light"
429- data-lang = " en"
430- crossorigin = " anonymous"
431- async
432- >
507+ <div class = " giscus-image-container" >
508+ <Image src = " /images/guest-book.png" alt = " Guest Book @8cat.life" width = { 1200 } height = { 630 } />
509+ </div >
510+ <div class = " giscus-comments-container" >
511+ <script
512+ is :inline
513+ src = " https://giscus.app/client.js"
514+ data-repo = " CatCodeMe/catcodeme.github.io"
515+ data-repo-id = " R_kgDOLTuIuQ"
516+ data-category = " General"
517+ data-category-id = " DIC_kwDOLTuIuc4Csz7O"
518+ data-mapping = " og:title"
519+ data-strict = " 0"
520+ data-reactions-enabled = " 1"
521+ data-emit-metadata = " 0"
522+ data-input-position = " top"
523+ data-theme = " light"
524+ data-lang = " en"
525+ crossorigin = " anonymous"
526+ async
527+ >
528+ </script >
529+ </div >
530+ <script is :inline >
531+ function applyHighlight() {
532+ const hash = window .location .hash .substring (1 );
533+ document .querySelectorAll (' .comment-item.highlighted-comment' ).forEach (el => {
534+ el .classList .remove (' highlighted-comment' );
535+ });
536+ if (hash ) {
537+ const targetComment = document .getElementById (hash );
538+ if (targetComment ) {
539+ targetComment .classList .add (' highlighted-comment' );
540+ // Optional: Scroll into view smoothly if not already in view
541+ targetComment .scrollIntoView ({ behavior: ' smooth' , block: ' center' });
542+ // Remove highlight after a short delay
543+ setTimeout (() => {
544+ targetComment .classList .remove (' highlighted-comment' );
545+ }, 2000 ); // 2 seconds
546+ }
547+ }
548+ }
549+
550+ // Apply highlight on initial load if hash is present
551+ document.addEventListener('DOMContentLoaded', applyHighlight);
552+ // Apply highlight when hash changes (e.g., clicking a reply link)
553+ window.addEventListener('hashchange', applyHighlight);
554+
555+ // Listen for clicks on internal anchor links within the comments list
556+ document.addEventListener('click', (event) => {
557+ const target = event .target ;
558+ // Check if the clicked element is an anchor tag within a .quoted-reply and its href starts with '#'
559+ if (target .tagName === ' A' && target .closest (' .quoted-reply' ) && target .getAttribute (' href' )?.startsWith (' #' )) {
560+ const hash = target .getAttribute (' href' );
561+ if (hash && window.location.hash !== hash ) { // Only update if hash is different to avoid unnecessary history entries
562+ event .preventDefault (); // Prevent default browser jump
563+ window .location .hash = hash ; // Manually update hash, which should trigger hashchange
564+ } else if (hash && window.location.hash === hash ) {
565+ // If clicking the same hash, hashchange won't fire, so manually apply highlight
566+ event .preventDefault (); // Prevent default browser jump
567+ applyHighlight ();
568+ }
569+ }
570+ } );
433571 </script >
434572 </div >
435573 < / div >
436574 < / body >
437- < / html >
575+ < / html >
0 commit comments