Skip to content

Commit

Permalink
Move copyButton handling to a higher level component
Browse files Browse the repository at this point in the history
  • Loading branch information
DukeManh committed Mar 28, 2022
1 parent 8f4ced1 commit d721778
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 58 deletions.
57 changes: 0 additions & 57 deletions src/web/app/src/components/Posts/Post.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useRef, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import useSWR from 'swr';
import clsx from 'clsx';
import { makeStyles, Theme, useTheme } from '@material-ui/core/styles';
Expand All @@ -16,7 +15,6 @@ import {
Chip,
} from '@material-ui/core';
import ErrorRoundedIcon from '@material-ui/icons/ErrorRounded';
import FileCopyIcon from '@material-ui/icons/FileCopy';
import LiteYouTubeEmbed from 'react-lite-youtube-embed';
import GitHubInfo from './GitHubInfo';
import YouTubeInfo from './YouTubeInfo';
Expand Down Expand Up @@ -328,13 +326,6 @@ const zoomInImage = (img: HTMLImageElement) => {
document.body.appendChild(zoomedImgContainer);
};

function copyCode(btn: HTMLElement) {
const nextNode = btn.nextElementSibling;
if (nextNode?.textContent) {
navigator.clipboard.writeText(nextNode.textContent);
}
}

function handleClick(e: MouseEvent) {
// zoom out of all the currently zoomed images, if zoomed out, don't do anything.
if (zoomOutAllImages()) return;
Expand All @@ -346,46 +337,6 @@ function handleClick(e: MouseEvent) {
}
}

function isCodeSnippet(elem: Element) {
return elem.tagName === 'CODE' && elem.parentElement?.tagName === 'PRE';
}

function createCopyButton(e: MouseEvent) {
const event = e.target;
if (event instanceof HTMLElement && isCodeSnippet(event)) {
const parentDiv = event.parentNode;
const previousNode = event.previousElementSibling;
if (previousNode?.className !== 'copyCodeBtn') {
const elem = document.createElement('button');
elem.className = 'copyCodeBtn';
elem.onclick = () => copyCode(elem);
parentDiv?.insertBefore(elem, event);
ReactDOM.render(<FileCopyIcon />, elem);
}
}
}

function removeCopyButton(e: MouseEvent) {
const copyButtons = document.querySelectorAll<HTMLDivElement>('.copyCodeBtn');
copyButtons.forEach((elem) => {
elem.parentNode?.removeChild(elem);
const event = e.target;
if (event instanceof HTMLElement && isCodeSnippet(event)) {
event.parentElement?.removeEventListener('mouseleave', removeCopyButton);
}
});
}

function handleMouseMove(e: MouseEvent) {
// if mouse hovers <code></code>, we call createCopyButton(e)
if (e.target instanceof HTMLElement && isCodeSnippet(e.target)) {
e.preventDefault();
createCopyButton(e);
// if mouse leaves <pre></pre> => remove any copy button
e.target?.parentElement?.addEventListener('mouseleave', removeCopyButton);
}
}

const PostComponent = ({ postUrl, currentPost, totalPosts }: Props) => {
const classes = useStyles();
const theme = useTheme();
Expand Down Expand Up @@ -423,14 +374,6 @@ const PostComponent = ({ postUrl, currentPost, totalPosts }: Props) => {
};
}, []);

// Listen for mouse move events
useEffect(() => {
window.document.addEventListener('mousemove', handleMouseMove);
return () => {
window.document.removeEventListener('mousemove', handleMouseMove);
};
}, []);

if (error) {
console.error(`Error loading post at ${postUrl}`, error);
return (
Expand Down
58 changes: 57 additions & 1 deletion src/web/app/src/components/Posts/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ReactElement } from 'react';
import { ReactElement, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { Container, createStyles } from '@material-ui/core';
import FileCopyIcon from '@material-ui/icons/FileCopy';
import { makeStyles, Theme } from '@material-ui/core/styles';
import PostComponent from './Post';
import { Post } from '../../interfaces';
Expand Down Expand Up @@ -37,9 +39,63 @@ const useStyles = makeStyles((theme: Theme) =>
})
);

function copyCode(codeSnippet: string) {
if (navigator) {
navigator.clipboard.writeText(codeSnippet);
}
}

function isCodeBlock(elem: Element) {
return elem.tagName === 'CODE' && elem.parentElement?.tagName === 'PRE';
}

function createButton(onClick: (e: MouseEvent) => void) {
const elem = document.createElement('button');
elem.className = 'copyCodeBtn';
elem.onclick = onClick;
return elem;
}

function removeButton(parent: HTMLElement) {
parent.querySelectorAll('.copyCodeBtn').forEach((button) => button.remove());
}

function handleMouseMove(e: MouseEvent) {
// if mouse hovers <code></code>, we call createCopyButton(e)
if (e.target instanceof HTMLElement && isCodeBlock(e.target)) {
e.preventDefault();
const snippet = e.target; // code tag
const parentDiv = snippet.parentElement; // pre tag
const previousNode = snippet.previousElementSibling;

// check if a button has already been added
if (previousNode?.className === 'copyCodeBtn') {
return;
}

// there's nothing to copy
if (!parentDiv || !snippet.textContent) {
return;
}

const copyButton = createButton(() => copyCode(snippet.textContent!));
parentDiv.insertBefore(copyButton, snippet);
ReactDOM.render(<FileCopyIcon />, copyButton); // render JSX icon into the pure HTML button
parentDiv.onmouseleave = () => removeButton(parentDiv);
}
}

const Timeline = ({ pages, totalPosts, nextPage }: Props) => {
const classes = useStyles();

// Listen for mouse move events
useEffect(() => {
window.document.addEventListener('mousemove', handleMouseMove);
return () => {
window.document.removeEventListener('mousemove', handleMouseMove);
};
}, []);

if (!pages) {
return null;
}
Expand Down

0 comments on commit d721778

Please sign in to comment.