Skip to content

Commit

Permalink
feat: add a copy button to the code block #1211
Browse files Browse the repository at this point in the history
  • Loading branch information
shuashuai committed Dec 27, 2024
1 parent 1dd7a8d commit 7a15e24
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 12 deletions.
2 changes: 2 additions & 0 deletions i18n/en_US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2300,5 +2300,7 @@ ui:
user_deleted: This user has been deleted.
badge_activated: This badge has been activated.
badge_inactivated: This badge has been inactivated.
copy: Copy to clipboard
copied: Copied


3 changes: 2 additions & 1 deletion i18n/zh_CN.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2262,5 +2262,6 @@ ui:
user_deleted: 此用户已被删除
badge_activated: 此徽章已被激活。
badge_inactivated: 此徽章已被禁用。

copy: 复制
copied: 已复制

7 changes: 6 additions & 1 deletion ui/src/components/Editor/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
memo,
useImperativeHandle,
} from 'react';
import { useTranslation } from 'react-i18next';

import { markdownToHtml } from '@/services';
import ImgViewer from '@/components/ImgViewer';
Expand All @@ -37,6 +38,7 @@ let renderTimer;
const Index = ({ value }, ref) => {
const [html, setHtml] = useState('');
const previewRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation('translation', { keyPrefix: 'messages' });

const renderMarkdown = (markdown) => {
clearTimeout(renderTimer);
Expand All @@ -59,7 +61,10 @@ const Index = ({ value }, ref) => {

previewRef.current?.scrollTo(0, scrollTop);

htmlRender(previewRef.current);
htmlRender(previewRef.current, {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, [html]);

useImperativeHandle(ref, () => {
Expand Down
58 changes: 57 additions & 1 deletion ui/src/components/Editor/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,25 @@ import { EditorState, Compartment } from '@codemirror/state';
import { EditorView, placeholder } from '@codemirror/view';
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
import { languages } from '@codemirror/language-data';
import copy from 'copy-to-clipboard';
import Tooltip from 'bootstrap/js/dist/tooltip';

import { Editor } from '../types';
import { isDarkTheme } from '@/utils/common';

import createEditorUtils from './extension';

const editableCompartment = new Compartment();
export function htmlRender(el: HTMLElement | null) {
interface htmlRenderConfig {
copyText: string;
copySuccessText: string;
}
export function htmlRender(el: HTMLElement | null, config?: htmlRenderConfig) {
if (!el) return;
const { copyText = '', copySuccessText = '' } = config || {
copyText: 'Copy to clipboard',
copySuccessText: 'Copied!',
};
// Replace all br tags with newlines
// Fixed an issue where the BR tag in the editor block formula HTML caused rendering errors.
el.querySelectorAll('p').forEach((p) => {
Expand Down Expand Up @@ -69,6 +79,52 @@ export function htmlRender(el: HTMLElement | null) {
a.rel = 'nofollow';
}
});

// Add copy button to all pre tags
el.querySelectorAll('pre').forEach((pre) => {
// Create copy button
const codeWrap = document.createElement('div');
codeWrap.className = 'position-relative a-code-wrap';
const codeTool = document.createElement('div');
codeTool.className = 'a-code-tool';
const uniqueId = `a-copy-code-${Date.now().toString().substring(5)}-${Math.floor(Math.random() * 10)}${Math.floor(Math.random() * 10)}${Math.floor(Math.random() * 10)}`;
const str = `
<button type="button" class="btn btn-dark rounded-0 a-copy-code" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="${copyText}" id="${uniqueId}">
<i class="br bi-copy"></i>
</button>
`;
codeTool.innerHTML = str;

// Add copy button to pre tag
pre.style.position = 'relative';

// 将 codeTool 和 pre 插入到 codeWrap 中, 并且使用 codeWrap 替换 pre
codeWrap.appendChild(codeTool);
pre.parentNode?.replaceChild(codeWrap, pre);
codeWrap.appendChild(pre);

const tooltipTriggerList = el.querySelectorAll('.a-copy-code');

console.log('tooltipTriggerList', Array.from(tooltipTriggerList).length);
Array.from(tooltipTriggerList)?.map(
(tooltipTriggerEl) => new Tooltip(tooltipTriggerEl),
);

// Copy pre content on button click
const copyBtn = codeTool.querySelector('.a-copy-code');
copyBtn?.addEventListener('click', () => {
const textToCopy = pre.textContent || '';
copy(textToCopy);
// Change tooltip text on copy success
const tooltipInstance = Tooltip.getOrCreateInstance(`#${uniqueId}`);
tooltipInstance?.setContent({ '.tooltip-inner': copySuccessText });
const myTooltipEl = document.querySelector(`#${uniqueId}`);
myTooltipEl?.addEventListener('hidden.bs.tooltip', () => {
console.log('hidden.bs.tooltip');
tooltipInstance.setContent({ '.tooltip-inner': copyText });
});
});
});
}

export const useEditor = ({
Expand Down
21 changes: 21 additions & 0 deletions ui/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,24 @@ img[src=""] {
.mb-12 {
margin-bottom: 12px;
}

.a-code-wrap:hover .a-code-tool {
display: block;
}
.a-code-tool {
position: absolute;
top: 0;
right: 0;
font-size: 16px;
z-index: 1;
display: none;
.a-copy-code {
line-height: 24px;
width: 1.5rem;
height: 1.5rem;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
}
}
5 changes: 4 additions & 1 deletion ui/src/pages/Legal/Privacy/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ const Index: FC = () => {
if (!fmt) {
return;
}
htmlRender(fmt);
htmlRender(fmt, {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, [privacy?.privacy_policy_parsed_text]);

try {
Expand Down
5 changes: 4 additions & 1 deletion ui/src/pages/Legal/Tos/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ const Index: FC = () => {
if (!fmt) {
return;
}
htmlRender(fmt);
htmlRender(fmt, {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, [tos?.terms_of_service_parsed_text]);

try {
Expand Down
9 changes: 7 additions & 2 deletions ui/src/pages/Questions/Detail/components/Answer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,13 @@ const Index: FC<Props> = ({
return;
}

htmlRender(answerRef.current.querySelector('.fmt'));
htmlRender(answerRef.current.querySelector('.fmt'), {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, [answerRef.current]);

useEffect(() => {
if (aid === data.id) {
setTimeout(() => {
const element = answerRef.current;
Expand All @@ -87,7 +92,7 @@ const Index: FC<Props> = ({
}
}, 100);
}
}, [data.id, answerRef.current]);
}, [data.id]);

if (!data?.id) {
return null;
Expand Down
5 changes: 4 additions & 1 deletion ui/src/pages/Questions/Detail/components/Question/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ const Index: FC<Props> = ({ data, initPage, hasAnswer, isLogged }) => {
return;
}

htmlRender(ref.current);
htmlRender(ref.current, {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, [ref.current]);

if (!data?.id) {
Expand Down
5 changes: 4 additions & 1 deletion ui/src/pages/Questions/EditAnswer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ const Index = () => {
if (!questionContentRef?.current) {
return;
}
htmlRender(questionContentRef.current);
htmlRender(questionContentRef.current, {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, [questionContentRef]);

usePromptWithUnload({
Expand Down
5 changes: 4 additions & 1 deletion ui/src/pages/Review/components/FlagContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ const Index: FC<IProps> = ({ refreshCount }) => {
}

setTimeout(() => {
htmlRender(ref.current);
htmlRender(ref.current, {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, 70);
}, [ref.current]);

Expand Down
5 changes: 4 additions & 1 deletion ui/src/pages/Review/components/QueuedContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ const Index: FC<IProps> = ({ refreshCount }) => {
}

setTimeout(() => {
htmlRender(ref.current);
htmlRender(ref.current, {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, 70);
}, [ref.current]);

Expand Down
5 changes: 4 additions & 1 deletion ui/src/pages/Tags/Info/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ const TagIntroduction = () => {
if (!fmt) {
return;
}
htmlRender(fmt);
htmlRender(fmt, {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, [tagInfo?.parsed_text]);

if (!tagInfo) {
Expand Down

0 comments on commit 7a15e24

Please sign in to comment.