Skip to content

Commit f25a6df

Browse files
committed
feat(notes): add copy to clipboard when encountering markdown in notes
1 parent a587476 commit f25a6df

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { AfterViewChecked, Directive, ElementRef } from '@angular/core';
2+
3+
@Directive({
4+
selector: '[appCopyCodeButton]',
5+
})
6+
export class CopyCodeButtonDirective implements AfterViewChecked {
7+
private static stylesInjected = false;
8+
9+
constructor(private el: ElementRef) {
10+
CopyCodeButtonDirective.injectStyles();
11+
}
12+
13+
ngAfterViewChecked(): void {
14+
const preElements = this.el.nativeElement.querySelectorAll('pre');
15+
preElements.forEach((pre: HTMLElement) => {
16+
if (pre.getAttribute('data-copy-btn-added') === 'true') {
17+
return;
18+
}
19+
20+
pre.setAttribute('data-copy-btn-added', 'true');
21+
pre.style.position = 'relative';
22+
23+
const button = document.createElement('button');
24+
button.className = 'copy-code-btn';
25+
button.title = 'Copy code';
26+
button.innerHTML = '<i class="far fa-copy"></i>';
27+
28+
button.addEventListener('click', () => {
29+
const code = pre.querySelector('code');
30+
const text = code ? code.textContent : pre.textContent;
31+
navigator.clipboard.writeText(text || '').then(() => {
32+
button.innerHTML = '<i class="fas fa-check"></i>';
33+
setTimeout(() => {
34+
button.innerHTML = '<i class="far fa-copy"></i>';
35+
}, 1500);
36+
});
37+
});
38+
39+
pre.appendChild(button);
40+
});
41+
}
42+
43+
private static injectStyles(): void {
44+
if (CopyCodeButtonDirective.stylesInjected) {
45+
return;
46+
}
47+
CopyCodeButtonDirective.stylesInjected = true;
48+
49+
const style = document.createElement('style');
50+
style.textContent = `
51+
pre[data-copy-btn-added="true"] {
52+
position: relative;
53+
}
54+
55+
.copy-code-btn {
56+
position: absolute;
57+
top: 4px;
58+
right: 4px;
59+
display: none;
60+
align-items: center;
61+
justify-content: center;
62+
width: 32px;
63+
height: 32px;
64+
padding: 0;
65+
border: 1px solid rgba(0, 0, 0, 0.15);
66+
border-radius: 4px;
67+
background: rgba(255, 255, 255, 0.9);
68+
color: #555;
69+
font-size: 14px;
70+
cursor: pointer;
71+
z-index: 10;
72+
transition: background-color 0.15s ease, color 0.15s ease;
73+
}
74+
75+
.copy-code-btn:hover {
76+
background: #fff;
77+
color: #000;
78+
border-color: rgba(0, 0, 0, 0.3);
79+
}
80+
81+
pre[data-copy-btn-added="true"]:hover .copy-code-btn {
82+
display: flex;
83+
}
84+
`;
85+
document.head.appendChild(style);
86+
}
87+
}
88+

apps/codever-ui/src/app/shared/note-details/note-card-body/note-content.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22

3-
<div #noteContentDiv appMarkedImageWidth>
3+
<div #noteContentDiv appMarkedImageWidth appCopyCodeButton>
44
<div *ngIf="partOfList && viewHeight > maxNoteHeightInList && !isFullScreen; else wholeText">
55
<div [ngClass]="{ more_text: showMoreText, less_text_note: !showMoreText }">
66
<p [innerHtml]="note.content | md2html"></p>

apps/codever-ui/src/app/shared/shared.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { NoteDetailsComponent } from './note-details/note-details.component';
4141
import { NoteContentComponent } from './note-details/note-card-body/note-content.component';
4242
import { AsyncNoteListComponent } from './async-note-list/async-note-list.component';
4343
import { MarkedImageWidthDirective } from './directive/marked-image-width.directive';
44+
import { CopyCodeButtonDirective } from './directive/copy-code-button.directive';
4445
import { SnippetSocialShareDialogComponent } from './dialog/snippet-social-share-dialog/snippet-social-share-dialog.component';
4546
import { SnippetSocialShareDialogContentComponent } from './dialog/snippet-social-share-dialog/snippet-social-share-dialog-content/snippet-social-share-dialog-content.component';
4647
import { HighLightPipe } from '../common/pipes/highlight.pipe';
@@ -79,6 +80,7 @@ import { OpenInNewTabDirective } from './directive/open-in-new-tab.directive';
7980
TagsValidatorDirective,
8081
OpenInNewTabDirective,
8182
MarkedImageWidthDirective,
83+
CopyCodeButtonDirective,
8284
DeleteResourceDialogComponent,
8385
SocialShareDialogComponent,
8486
SnippetSocialShareDialogComponent,

0 commit comments

Comments
 (0)