Header.tsx 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { useCallback, useEffect, useState } from 'react';
  2. import EventEmitter from 'events';
  3. import { useRouter } from 'next/router';
  4. import { Element } from 'react-markdown/lib/rehype-filter';
  5. import { NextLink } from './NextLink';
  6. import styles from './Header.module.scss';
  7. declare const globalEmitter: EventEmitter;
  8. function setCaretLine(line?: number): void {
  9. if (line != null) {
  10. globalEmitter.emit('setCaretLine', line);
  11. }
  12. }
  13. type EditLinkProps = {
  14. line?: number,
  15. }
  16. /**
  17. * Inner FC to display edit link icon
  18. */
  19. const EditLink = (props: EditLinkProps): JSX.Element => {
  20. const isDisabled = props.line == null;
  21. return (
  22. <span className="revision-head-edit-button">
  23. <a href="#edit" aria-disabled={isDisabled} onClick={() => setCaretLine(props.line)}>
  24. <i className="icon-note"></i>
  25. </a>
  26. </span>
  27. );
  28. };
  29. type HeaderProps = {
  30. children: React.ReactNode,
  31. node: Element,
  32. level: number,
  33. id?: string,
  34. }
  35. export const Header = (props: HeaderProps): JSX.Element => {
  36. const {
  37. node, id, children, level,
  38. } = props;
  39. const router = useRouter();
  40. const [isActive, setActive] = useState(false);
  41. const CustomTag = `h${level}` as keyof JSX.IntrinsicElements;
  42. const activateByHash = useCallback((url: string) => {
  43. const hash = (new URL(url, 'https://example.com')).hash.slice(1);
  44. setActive(hash === id);
  45. }, [id]);
  46. // init
  47. useEffect(() => {
  48. activateByHash(window.location.href);
  49. }, [activateByHash]);
  50. // update isActive when hash is changed
  51. useEffect(() => {
  52. router.events.on('hashChangeComplete', activateByHash);
  53. return () => {
  54. router.events.off('hashChangeComplete', activateByHash);
  55. };
  56. }, [activateByHash, router.events]);
  57. return (
  58. <CustomTag id={id} className={`revision-head ${styles['revision-head']} ${isActive ? 'blink' : ''}`}>
  59. {children}
  60. <NextLink href={`#${id}`} className="revision-head-link">
  61. <span className="icon-link"></span>
  62. </NextLink>
  63. <EditLink line={node.position?.start.line} />
  64. </CustomTag>
  65. );
  66. };