Skip to content

Commit

Permalink
feat[closes #93]: add copy to clipboard to code blocks (#101)
Browse files Browse the repository at this point in the history
* fix/recipe: replace entrypoint parameter with cmd

* test: updated cmd syntax

* fix: try old method for entrypoint

* fix: contrast issues

See https://pagespeed.web.dev/analysis/https-docs-vanillaos-org-docs-en-vso-manpage/wss0i631wy?form_factor=mobile

* feat: add copy to clipboard to code blocks

* fix: make the copy button not move when scrolling

---------

Co-authored-by: K.B.Dharun Krishna <[email protected]>
  • Loading branch information
GabsEdits and kbdharun authored Jul 23, 2024
1 parent 0e9d868 commit a73135d
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 35 deletions.
16 changes: 10 additions & 6 deletions src/assets/css/style.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
::-webkit-scrollbar {
width: 8px;
height: 8px;
width: 8px;
height: 8px;
}

::-webkit-scrollbar-track {
background: #f1f1f1;
background: #f1f1f1;
}

::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
background: #888;
border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
background: #555;
background: #555;
}

.pre-div:hover button {
@apply opacity-100;
}


Expand Down
127 changes: 98 additions & 29 deletions src/views/ArticleView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
<li v-if="collectionName">
<router-link :to="{ name: 'collection', params: { collection: collectionName } }"
class="text-blue-600 hover:text-blue-800 dark:hover:text-blue-400 dark:text-blue-400">{{
collectionName }}</router-link>
collectionName }}</router-link>
</li>
<li v-if="collectionName">
<span class="material-icons text-gray-500 dark:text-gray-300">chevron_right</span>
</li>
<li>
<a class="cursor-pointer text-gray-600 dark:text-gray-400">{{ lang }}</a>
<a class="cursor-pointer text-gray-600 dark:text-gray-400">{{
lang
}}</a>
</li>
<li v-if="article.Title" class="hidden md:flex">
<span class="material-icons text-gray-500 dark:text-gray-300">chevron_right</span>
Expand All @@ -40,7 +42,7 @@
:href="`https://github.com/${author}`" target="_blank">
<img v-if="!imageError[author]" :src="`https://github.com/${author}.png?size=40`" :alt="author"
class="w-7 h-7 rounded-full object-cover hover:shadow-lg transition-shadow"
@error="imageError[author] = true" :title="author">
@error="imageError[author] = true" :title="author" />
<span v-else
class="w-7 h-7 rounded-full overflow-hidden bg-blue-500 dark:bg-blue-700 text-white font-bold inline-flex items-center justify-center -mr-3 hover:shadow-lg transition-shadow">
{{ getInitials(author) }}
Expand All @@ -59,13 +61,15 @@
<aside class="hidden lg:block lg:w-1/4">
<div class="sticky top-4 z-1">
<div class="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-600 p-4 rounded-lg">
<p class="font-semibold mb-4 text-gray-900 dark:text-gray-200">Navigation</p>
<p class="font-semibold mb-4 text-gray-900 dark:text-gray-200">
Navigation
</p>
<ul class="space-y-2 max-h-[85vh] overflow-y-scroll">
<li v-for="(heading, index) in headings" :key="index" :style="heading.style"
class="text-gray-900 dark:text-gray-200">
<a @click="scrollToHeading(heading.id)"
class="cursor-pointer text-blue-600 hover:text-blue-800 dark:hover:text-blue-400 dark:text-blue-400">{{
heading.text }}</a>
heading.text }}</a>
</li>
</ul>
<a v-if="editUrl != ''" :href="editUrl" target="_blank"
Expand Down Expand Up @@ -117,13 +121,15 @@
class="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden bottom-0 flex flex-col items-end justify-end">
<aside class="relative bg-white dark:bg-gray-700 p-4 shadow-lg overflow-auto w-full rounded-t-lg pb-16">
<div class="p-2">
<p class="font-semibold mb-4 text-gray-900 dark:text-gray-200">Navigation</p>
<p class="font-semibold mb-4 text-gray-900 dark:text-gray-200">
Navigation
</p>
<ul class="space-y-2">
<li v-for="(heading, index) in headings" :key="index" :style="heading.style"
class="text-gray-900 dark:text-gray-200">
<a @click="scrollToHeading(heading.id)"
class="cursor-pointer text-blue-600 hover:text-blue-800 dark:hover:text-blue-400 dark:text-blue-400">{{
heading.text }}</a>
heading.text }}</a>
</li>
</ul>
<a v-if="editUrl != ''" :href="editUrl" target="_blank"
Expand All @@ -139,15 +145,20 @@
import { defineComponent, reactive, watch } from "vue";
import type { Article, ChronosCollection, ChronosConfig } from "@/core/models";
import { useChronosStore } from "@/core/store";
import { useHead } from 'unhead';
import { useHead } from "unhead";
export default defineComponent({
name: "ArticleView",
data() {
return {
article: {} as Article,
lang: "",
headings: [] as { text: string; id: string; level: number, style: string }[],
headings: [] as {
text: string;
id: string;
level: number;
style: string;
}[],
chronosConfig: {} as ChronosConfig,
collectionName: "",
editUrl: "",
Expand All @@ -162,7 +173,7 @@ export default defineComponent({
return useChronosStore();
},
readingTime() {
if (!this.article || !this.article.Body) return '0m 0s';
if (!this.article || !this.article.Body) return "0m 0s";
const wordCount = this.article.Body.split(/\s+/).length;
Expand All @@ -177,9 +188,11 @@ export default defineComponent({
async mounted() {
await this.initializeArticle();
const collectionConfig = this.chronosConfig.chronosCollections.find(
(c) => c.shortName === this.collectionName
(c) => c.shortName === this.collectionName,
);
this.addCopyToClipboardToPres();
document.getElementById(window.location.hash.slice(1))?.scrollIntoView({behavior: 'smooth'});
this.setEditUrl(collectionConfig);
},
Expand Down Expand Up @@ -210,7 +223,7 @@ export default defineComponent({
const response = await this.$chronosAPI.getArticleByLanguageAndSlug(
lang,
this.$route.params.slug,
this.collectionName
this.collectionName,
);
this.handleArticleResponse(response, lang);
},
Expand All @@ -223,7 +236,7 @@ export default defineComponent({
const response = await this.$chronosAPI.getArticleByLanguageAndSlug(
lang,
slug,
this.collectionName
this.collectionName,
);
this.handleArticleResponse(response, lang);
this.setNextAndPreviousArticles();
Expand All @@ -234,19 +247,20 @@ export default defineComponent({
if (this.article.Previous && this.article.Previous !== "") {
// @ts-ignore
this.previousArticle = await this.$chronosAPI.getArticleByLanguageAndSlug(
this.lang,
this.article.Previous,
this.collectionName
);
this.previousArticle =
await this.$chronosAPI.getArticleByLanguageAndSlug(
this.lang,
this.article.Previous,
this.collectionName,
);
}
if (this.article.Next && this.article.Next !== "") {
// @ts-ignore
this.nextArticle = await this.$chronosAPI.getArticleByLanguageAndSlug(
this.lang,
this.article.Next,
this.collectionName
this.collectionName,
);
}
Expand All @@ -269,11 +283,20 @@ export default defineComponent({
title: `${this.article!.Title} - ${this.chronosConfig.title}`,
meta: [
{ name: "description", content: this.article!.Description },
{ name: "og:title", content: `${this.article!.Title} - ${this.chronosConfig.title}` },
{
name: "og:title",
content: `${this.article!.Title} - ${this.chronosConfig.title}`,
},
{ name: "og:description", content: this.article!.Description },
{ name: "og:url", content: `${this.chronosConfig.baseURL}/${this.article!.Slug}` },
{
name: "og:url",
content: `${this.chronosConfig.baseURL}/${this.article!.Slug}`,
},
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:title", content: `${this.article!.Title} - ${this.chronosConfig.title}` },
{
name: "twitter:title",
content: `${this.article!.Title} - ${this.chronosConfig.title}`,
},
{ name: "twitter:description", content: this.article!.Description },
],
});
Expand All @@ -289,7 +312,9 @@ export default defineComponent({
const hTags = doc.querySelectorAll("h1, h2, h3, h4, h5, h6");
hTags.forEach((hTag) => {
hTag.id = hTag.textContent?.replace(/\s+/g, "-").toLowerCase() as string;
hTag.id = hTag.textContent
?.replace(/\s+/g, "-")
.toLowerCase() as string;
});
return doc.body.innerHTML;
Expand All @@ -298,7 +323,10 @@ export default defineComponent({
watch(
() => this.$route,
async (newRoute, oldRoute) => {
if (newRoute.params.slug !== oldRoute.params.slug || newRoute.params.lang !== oldRoute.params.lang) {
if (
newRoute.params.slug !== oldRoute.params.slug ||
newRoute.params.lang !== oldRoute.params.lang
) {
const lang = Array.isArray(newRoute.params.lang)
? newRoute.params.lang[0]
: newRoute.params.lang;
Expand All @@ -307,7 +335,7 @@ export default defineComponent({
: newRoute.params.slug;
await this.loadArticle(lang, slug);
}
}
},
);
},
setEditUrl(collectionConfig: ChronosCollection | undefined) {
Expand All @@ -318,7 +346,12 @@ export default defineComponent({
}
},
extractHeadings(body: string) {
const headings = [] as { text: string; id: string; level: number, style: string }[];
const headings = [] as {
text: string;
id: string;
level: number;
style: string;
}[];
const parser = new DOMParser();
const doc = parser.parseFromString(body, "text/html");
const hTags = doc.querySelectorAll("h1, h2, h3, h4, h5, h6");
Expand All @@ -340,6 +373,10 @@ export default defineComponent({
const offset = 53;
const element = document.getElementById(headingId);
const y = element?.getBoundingClientRect().top;
window.scrollTo({
top: y ? y + window.scrollY - offset : 0,
behavior: "smooth",
});
history.replaceState(null, null, "#" + headingId)
window.scrollTo({ top: y ? y + window.scrollY - offset : 0, behavior: "smooth" });
this.isSidebarVisible = false;
Expand All @@ -352,11 +389,43 @@ export default defineComponent({
return names[0].charAt(0);
},
onImageError(event: any, author: string) {
event.target.style.display = 'none';
event.target.style.display = "none";
},
printArticle() {
window.print()
}
window.print();
},
addCopyToClipboardToPres() {
const codeBlocks = document.querySelectorAll("pre");
codeBlocks.forEach((block) => {
const div = document.createElement("div");
div.className = "relative pre-div";
const pre = document.createElement("pre");
pre.textContent = block.textContent;
div.appendChild(pre);
const button = document.createElement("button");
const iconStyle = "p-0 m-0 text-lg leading-none";
button.className = "bg-blue-500 text-white p-[6px] rounded absolute right-2 top-2 opacity-0 transition-opacity size-fit flex items-center justify-center";
button.type = "button";
button.innerHTML = `<span class="material-icons ${iconStyle}">content_copy</span>`;
button.addEventListener("click", () => {
if (pre.textContent !== null) {
navigator.clipboard.writeText(pre.textContent);
}
button.classList.add("bg-green-600");
button.innerHTML = `<span class="material-icons ${iconStyle}">done</span>`;
setTimeout(() => {
button.classList.remove("bg-green-600");
button.innerHTML = `<span class="material-icons ${iconStyle}">content_copy</span>`;
}, 2000);
});
div.appendChild(button);
block.replaceWith(div);
});
},
},
});
</script>

0 comments on commit a73135d

Please sign in to comment.