NextLink.tsx 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import Link, { LinkProps } from 'next/link';
  2. import { useSiteUrl } from '~/stores/context';
  3. import loggerFactory from '~/utils/logger';
  4. const logger = loggerFactory('growi:components:NextLink');
  5. const isAnchorLink = (href: string): boolean => {
  6. return href.toString().length > 0 && href[0] === '#';
  7. };
  8. const isExternalLink = (href: string, siteUrl: string | undefined): boolean => {
  9. try {
  10. const baseUrl = new URL(siteUrl ?? 'https://example.com');
  11. const hrefUrl = new URL(href, baseUrl);
  12. return baseUrl.host !== hrefUrl.host;
  13. }
  14. catch (err) {
  15. logger.debug(err);
  16. return false;
  17. }
  18. };
  19. type Props = Omit<LinkProps, 'href'> & {
  20. children: React.ReactNode,
  21. href?: string,
  22. className?: string,
  23. };
  24. export const NextLink = (props: Props): JSX.Element => {
  25. const {
  26. href, children, className, ...rest
  27. } = props;
  28. const { data: siteUrl } = useSiteUrl();
  29. if (href == null) {
  30. return <a className={className}>{children}</a>;
  31. }
  32. // extract 'data-*' props
  33. const dataAttributes = Object.fromEntries(
  34. Object.entries(rest).filter(([key]) => key.startsWith('data-')),
  35. );
  36. // when href is an anchor link
  37. if (isAnchorLink(href)) {
  38. return (
  39. <a href={href} className={className} {...dataAttributes}>{children}</a>
  40. );
  41. }
  42. if (isExternalLink(href, siteUrl)) {
  43. return (
  44. <a href={href} className={className} target="_blank" rel="noopener noreferrer" {...dataAttributes}>
  45. {children}&nbsp;<i className='icon-share-alt small'></i>
  46. </a>
  47. );
  48. }
  49. return (
  50. <Link {...rest} href={href} prefetch={false}>
  51. <a href={href} className={className} {...dataAttributes}>{children}</a>
  52. </Link>
  53. );
  54. };