We recently used Vuepress to package the community’s typeScript translations into an online document, but some plug-ins the community didn’t have or didn’t implement for their own custom needs, so we decided to customize one. The culture uses the gold copy code feature as an example. Take a look at the finished interface
Take a look at the official architecture
Plug-ins run in the Node environment
CommonJS
Like Vue, Vuepress has a life cycle
- ready
This can be simply interpreted as initialization to complete the call
- updated
Page update call
- generated
The production environment build completes the call
Implementation approach
Vuepress will package the MD file into multiple HTML files, so we need to update our component after each change of the file address. According to the life cycle above, our requirements can be realized when updated
As for how to insert the copy-pasted component into the specified code, we can search all specified nodes after the page is loaded and then insert the component through appendChild
The project structure
Copy ├─ ├─ clipboard ├─ copy.vue ├─ index.jsCopy the code
- index.js
Export documents exposed
- copy.vue
Implement the copy code component
- clipboard.js
Responsible for implementing clipboard text
- clientRootMixin.js
Responsible for the implementation of inserting components into different pages
index.js
It says that we are going to develop a plug-in that duplicates the code, so let’s start by simply defining three parameters
- The first is the scope of the selector
- The second is to copy the text displayed by the code
- The third is a callback function that receives the change message and implements custom animations
In the official example given, there are two ways to use the plug-in
// 例1
module.exports = {
plugins: [["vuepress-plugin-xxx",
{
/* options */}}]].// 例2
module.exports = {
plugins: {
xxx: {
/* options */}}};Copy the code
As you can see, if there is an argument that can be passed in this way, then the first step of our plug-in definition is to handle this argument, or not accept it, if not, just return an object
module.exports = {
// ...
};
Copy the code
Let’s define a function that simply accepts options
/ / object type
module.exports = {
define: {
selector: options.selector || 'div[class*="language-"] pre'.copyText: options.copyText || "Copy code".change: options.change
}
};
// Function:
module.exports = (options, context) = > ({
define() {
return {
selector: options.selector || 'div[class*="language-"] pre'.copyText: options.copyText || "Copy code".change: options.change }; }});Copy the code
Note that the return must be in CommonJS form. Above, we define the global variable for internal use of our plug-in through the define attribute. It supports two forms of function and object, which can be understood as the data attribute of vue
This step is relatively simple and will not be explained too much, above we mentioned the need to inject components into all pages, this step is the responsibility of clientRootMixin.js, here is to implement it, we need to introduce it in index.js, a complete index.js should look like this
const path = require("path");
module.exports = (options = {}, ctx) = > ({
define: {
// ...
},
clientRootMixin: path.resolve(__dirname, "clientRootMixin.js")});Copy the code
clientRootMixin.js
ClientRootMixin allows us to control the lifecycle of the root component by listening for the updated event and then inserting copy.vue into the current page
import CodeCopy from "./copy.vue";
import Vue from "vue";
export default {
updated() {
// Wait for dom loading to complete
this.$nextTick((a)= > {
this.update();
});
},
methods: {
update() {
// Get all the DOM, then insert vue components on all the code blocks
const dom = Array.from(document.querySelectorAll(selector));
dom.forEach(el= > {
// Check whether the current node is already inserted
if (/v-copy/.test(el.className)) {
return;
}
// Create a copy component
const C = Vue.extend(CodeCopy);
const copy = new C();
// Below are the props and some private properties of the component
copy.copyText = copyText;
copy.code = el.textContent;
copy._parent = el;
copy.$mount();
el.className += ` v- copy`; el.appendChild(copy.$el); }); }}};Copy the code
OK, now that we’re done, how do I put code into the clipboard
clipboard.js
- Navigator.clipboard supports asynchronous clipboards
- Document.execcommand () is more compatible, but only synchronizes the clipboard
The above are the two native methods, but here because it is used as a library, compatibility issues need to be considered, so I choose the already packaged Clipboard. js as the implementation of copy and paste, the following is the specific packaging method, this step can be skipped
import ClipboardJS from "clipboard";
// Encapsulates the clipboard event
const btn = document.createElement("div");
btn.style.display = "none";
document.body.appendChild(btn);
function setUpText(text = "") {
return new Promise((resolve, reject) = > {
const cli = new ClipboardJS(btn, {
text() {
returntext; }});// Trigger the click event \
const click = new Event("click");
cli.on("success".function() {
resolve(text);
// Delete with or without success
cli.destroy();
});
cli.on("error".function(e) {
reject(e.action);
// Delete with or without success
cli.destroy();
});
btn.dispatchEvent(click);
});
}
export default setUpText;
Copy the code
OK, now that we’re basically done with the preparation, let’s get back to our familiar vUE component development
copy.vue
This step is simple, just define some CSS properties and execute clipboard.js when you click the button
<template> <span> <span ref="btn" class="v-copy-code-btn" @click="copyClick">{{ copyText }}</span> </span> </template> <script> import clipboard from "./clipboard"; Export default {props: {copyText: {type: String, default: "copy code"}, code: String}, methods: { copyClick() { clipboard(this.code); }}}; </script> <style> <! </style>Copy the code
The last
After this step, the code is published to NPM for everyone to use. This process is not described any more.
Finally, I am ready to find a job now. I would be grateful if you could push it inside