Develop a Utools Markdown editor plug-in using Vue3 + vite + elementUI

objective

Utools has been used by bloggers for a long time, and its members continue to 2024. It follows bloggers from Deepin to Manjaro, to Windows and to MacOS. It is very convenient in many scenarios, such as selecting text middle key translation, automatic formatting after copying JSON, regular expression matching gadgets, calculating manuscript paper, etc. It has been recommended to many colleagues and friends for its efficiency and convenience.

While using it, I also want to contribute to the community and realize some plug-ins to provide more convenience for myself and others. In order to achieve multi-terminal compatibility, Utools mainly uses JS to develop plug-ins. The interface UI and component interaction are no different from traditional web development, but more system capabilities can be used to do things that traditional web development can't do.

Development documentation

Utools developer documentation: u.tools/docs/developer/welcome.html
ElementUI development document: element-plus.org/#/zh-CN/component...
Vue3 development documentation: v3.vuejs.org/guide/introduction.ht...
Vite development documentation: vitejs.dev/config/

In the developer document, quick start only provides the call combination of native JS + utools capabilities. The goal of this document is to combine Vue3 with Utools development, integrate the popular ElementUI framework in China, and finally create a simple Markdown editor based on these technologies.

For specific codes, see:

Github: github.com/wangerzi/utools-vue3-ma...

Gitee: gitee.com/wangerzi/utools-vue3-mar...

The interface effect is as follows:

There are also some excellent open source plug-ins for reference:

github.com/xiaou66/utools-pictureB...

github.com/xkloveme/utools-calenda...

github.com/in3102/upassword

Integration of basic tools

The purpose of this section is to install the business framework and major dependencies to prepare for business implementation.

A blank item has only one readme MD and gitignore

The configuration and code execution at this stage have been put into github.com/wangerzi/utools-vue3-ma... , if you want to do research and development based on the same technology stack, you can directly download the code and change it.

Initialize project

First, this is a vue3 + vite project, according to the official Quick Start Guide , execute the following instructions under the project root directory. Pay attention to comparing the execution results with the node version

Where mv utools-vue3-markdown-editor / */ This is because the project is initialized in a subfolder and not in the home directory. Initialization is because all files in the directory will be deleted, and there are already files in the blank project git/README.md/.gitignore. Initializing to this directory will cause these data to be cleaned up to avoid risks, so it is created in the subdirectory.

$ node -v
v12.16.1

$ npm -v
6.13.4

$ npm init vite utools-vue3-markdown-editor -- --template vue
npx: 6 Installation succeeded, time 3.61 second
√ Select a framework: » vue
√ Select a variant: » vue

Scaffolding project in D:\phpStudy\WWW\github\utools-vue3-markdown-editor\utools-vue3-markdown-editor...

Done. Now run:

  cd utools-vue3-markdown-editor
  npm install
  npm run dev

$ mv utools-vue3-markdown-editor/* ./

$ rm -rf utools-vue3-markdown-editor\

$ npm install

> esbuild@0.12.18 postinstall D:\phpStudy\WWW\github\utools-vue3-markdown-editor\node_modules\esbuild
> node install.js

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN utools-vue3-markdown-editor@0.0.0 No repository field.
npm WARN utools-vue3-markdown-editor@0.0.0 No license field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.3.2 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

added 57 packages from 78 contributors in 10.725s

3 packages are looking for funding
  run `npm fund` for details


$ npm run dev

> utools-vue3-markdown-editor@0.0.0 dev D:\phpStudy\WWW\github\utools-vue3-markdown-editor
> vite

Pre-bundling dependencies:
  vue
(this will be run only when your dependencies or config have changed)

  vite v2.4.4 dev server running at:

  > Local: http://localhost:3000/
  > Network: use `--host` to expose

  ready in 1375ms.

Access at this time localhost:3000/ You will see the following interface, indicating that the project initialization is completed

Frame introduction

The next step is to introduce the element framework, mainly for reference Official installation documentation

Execute the following instructions

$ npm install element-plus --save
npm WARN utools-vue3-markdown-editor@0.0.0 No repository field.
npm WARN utools-vue3-markdown-editor@0.0.0 No license field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.3.2 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ element-plus@1.0.2-beta.70
added 9 packages from 6 contributors in 11.379s

4 packages are looking for funding
  run `npm fund` for details

On demand references and SASS

Since vite, webpack and other packaging tools will use tree shaking to eliminate unused code, making on-demand references can make the most of this function and reduce the packaging volume. ElementUI official also provides element on demand reference instructions.

The purpose of this step is to install the style import plug-in of vite, and install sass and sass loader to be compatible with sass loading. Execute the following instructions:

$ npm install vite-plugin-style-import -D
npm WARN utools-vue3-markdown-editor@0.0.0 No repository field.
npm WARN utools-vue3-markdown-editor@0.0.0 No license field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.3.2 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ vite-plugin-style-import@1.1.1
added 22 packages from 10 contributors in 3.215s

5 packages are looking for funding
  run `npm fund` for details

$ npm install sass sass-loader
npm WARN sass-loader@12.1.0 requires a peer of fibers@>= 3.1.0 but none is installed. You must install peer dependencies yourself.
npm WARN sass-loader@12.1.0 requires a peer of node-sass@^4.0.0 || ^5.0.0 || ^6.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN sass-loader@12.1.0 requires a peer of webpack@^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN utools-vue3-markdown-editor@0.0.0 No repository field.
npm WARN utools-vue3-markdown-editor@0.0.0 No license field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.3.2 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ sass@1.37.5
+ sass-loader@12.1.0
added 17 packages from 20 contributors in 3.052s

6 packages are looking for funding
  run `npm fund` for details

Edit vite config. JS, adjusted to the following format. There are two purposes in this step

  • Specify a clear development port, which will also be reflected in the development configuration of utools
  • Load the of elementUI on demand scss file processing
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import styleImport from 'vite-plugin-style-import'

// https://vitejs.dev/config/
export default defineConfig({
  base: './', // !!!!!!!  Very important, otherwise you can't access it after packaging!!!!!!!
  server: {
    port: 3000,
  },
  plugins: [
    vue(),
    styleImport({
      libs: [{
        libraryName: 'element-plus',
        esModule: true,
        ensureStyleFile: true,
        resolveStyle: (name) => {
          name = name.slice(3)
          return `element-plus/packages/theme-chalk/src/${name}.scss`
        },
        resolveComponent: (name) => {
          return `element-plus/lib/${name}`
        },
      }]
    })
  ]
})

Then, modify Src / main JS, and add the ElementUI plug-in and base scss

import { createApp } from 'vue'
import App from './App.vue'

import { ElButton, ElSelect } from 'element-plus'
import 'element-plus/packages/theme-chalk/src/base.scss'

const app = createApp(App);

app.component(ElButton.name, ElButton)
app.component(ElSelect.name, ElSelect)

app.mount('#app')

Introduction verification

In Src / components / HelloWorld Vue, change it

<template>
  <el-button>Hello World</el-button>
  <h1>{{ msg }}</h1>

  <p>
    <a href="https://vitejs.dev/guide/features.html" target="_blank">
      Vite Documentation
    </a>
    |
    <a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
  </p>

  <button type="button" @click="state.count++">
    count is: {{ state.count }}
  </button>
  <p>
    Edit
    <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
</template>

<script setup>
import { defineProps, reactive } from 'vue'

defineProps({
  msg: String
})

const state = reactive({ count: 0 })
</script>

<style scoped>
a {
  color: #42b983;
}
</style>

Execute npm run dev and access http://localhost:3000/ , seeing the ElementUI style button displayed on the page indicates success

utools development configuration

After the above steps are completed, the development framework and basic directory have been established, but the difference between utools and traditional web development is that it can make use of the capabilities of the client and can be called quickly in utools, so we need to define two file plugins JSON and preload JS is used to specify the configuration of the plug-in and encapsulate the client capabilities that the plug-in can use.

Official configuration document: u.tools/docs/developer/welcome.html

Since our debugging environment is running on localhost:3000, utools also takes this debugging requirement into account and can be configured according to the documentation

The plug-in to be implemented this time can be entered by two entrances, and two feature s can be defined

  • Enter the main page with the keyword "markdown editor"
  • Copy suffix After the md file, wake up utools, and we will automatically read and edit the corresponding file

Consider the packaged plugin JSON and preload js logo. Png needs to appear in the dist / directory, so I put these three files in public /. After packaging, these three files will appear in dist / plugin json , dist/preload.js dist/logo.png meets the packaging requirements. The target file to be loaded by utools is dist / index HTML, so plugin The main configuration in JSON writes index HTML.

logo go www.iconfont.cn/ I found something related to text editing and used it

public/plugin.json

{
  "main": "index.html",
  "logo": "logo.png",
  "platform": ["win32", "darwin", "linux"],
  "preload": "preload.js",
  "development": {
    "main": "http://127.0.0.1:3000"
  },
  "features": [
    {
      "code": "main",
      "explain": "A convenient markdown Editing tools",
      "cmds":["markdown editor "]
    },
    {
      "code": "copy",
      "explain": "Copy file preview and edit",
      "cmds": [
        {
          "type": "files",
          "label": "markdown File Preview",
          "fileType": "file",
          "match": "/\\.md$/i",
          "minLength": 1,
          "maxLength": 1
        }
      ]
    }
  ]
}

preload simply write a console first. The node / electron related capabilities need to be used, and then come back to complete it.

public/preload.js

console.log("preload js loaded")

Run npm run dev to try the following package:

$ npm run build

> utools-vue3-markdown-editor@0.0.0 build D:\phpStudy\WWW\github\utools-vue3-markdown-editor
> vite build

vite v2.4.4 building for production...
✓ 358 modules transformed.
dist/assets/logo.03d6d6da.png             6.69kb
dist/assets/element-icons.9c88a535.woff   24.24kb
dist/assets/element-icons.de5eb258.ttf    49.19kb
dist/index.html                           0.48kb
dist/assets/index.5be5297f.js             1.02kb / brotli: 0.52kb
dist/assets/index.66070cd5.css            52.22kb / brotli: 7.52kb
dist/assets/vendor.27ac3d2d.js            211.72kb / brotli: 63.94kb

Debugging and packaging plug-ins

First, search "developer tools" in the utools plug-in, open it and click new project to supplement relevant information

After npm build in the previous step, a dist / directory will be generated and the dist / plugin under it will be Drag JSON to the developer tool and click Run

Because it is debugging mode, and we are in plugin Development. JSP is developed in JSON Main is localhost:3000, so you need npm run dev to run devserver during debugging

$ npm run dev

> utools-vue3-markdown-editor@0.0.0 dev D:\phpStudy\WWW\github\utools-vue3-markdown-editor
> vite


  vite v2.4.4 dev server running at:

  > Local: http://localhost:3000/
  > Network: use `--host` to expose

  ready in 1238ms.

[@vue/compiler-sfc] <script setup> is still an experimental proposal.
Follow its status at https://github.com/vuejs/rfcs/pull/227.

[@vue/compiler-sfc] When using experimental features,
it is recommended to pin your vue dependencies to exact versions to avoid breakage.

[@vue/compiler-sfc] `defineProps` is a compiler macro and no longer needs to be imported.

Then enter the keyword "markdown" in utools to see the plug-in in dev state

After entering the plug-in, click the button in the upper right corner or ctrl+sfhit+i to enter the developer mode, where you can see preload JS runs normally and "preload js loaded" is output

Note: the specified plugin JSON is dev / plugin JSON, so public / plugin json , public/preload.js needs to be copied manually or repackaged by npm run build, and then the utools developer tool clicks the button to refresh the plugin JSON.

Function realization

The overall function is relatively simple. The left side is the editing area, which is realized by textarea, and the right side is the preview area. The results of markdown syntax in the editing area on the left are rendered in real time. Below are two control buttons, save and save respectively.

Installation call of dependent Library

$ npm i marked keyboardjs github-markdown-css highlight.js
npm WARN sass-loader@12.1.0 requires a peer of webpack@^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN utools-vue3-markdown-editor@0.0.0 No repository field.
npm WARN utools-vue3-markdown-editor@0.0.0 No license field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.3.2 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ marked@2.1.3
added 1 package from 1 contributor in 33.831s

6 packages are looking for funding
  run `npm fund` for details

Layout implementation

Since this application can be completed in a single page, Src / app is simply modified Vue and Src / components / HelloWorld Vue, where HelloWorld Vue was renamed editor in the project Vue, the directory structure is shown in the following figure:

Editor.vue

In the template part, the El row and El col of Element are used to plan the basic layout

<template>
  <div class="container">
    <el-divider content-position="center">{{props.path?('Current:'+props.path):'Temporary documents'}}</el-divider>
    <el-row :gutter="30">
      <el-col :span="12">
        <el-input type="textarea" placeholder="markdown..." resize="none" :rows="19" :autofocus="true" v-model="state.content"></el-input>
      </el-col>
      <el-col :span="12">
        <div class="rendered markdown-body" v-html="renderedContent"></div>
      </el-col>
    </el-row>

    <el-row justify="center" :gutter="30">
      <el-col :span="6">
        <el-button class="save-button" @click="handleSave">{{saveText}}</el-button>
      </el-col>
      <el-col :span="6">
        <el-button class="save-button" @click="handleSaveAs">{{saveAsText}}</el-button>
      </el-col>
    </el-row>
  </div>
</template>

In the style part, some shadow, height and line feed restrictions are manually made

<style scoped>
.el-row {
  margin-bottom: 20px;
}
.container {
  width: 90%;
  margin: 20px auto;
}
.rendered {
  /*height: calc(100% - 20px);*/

  height: calc(407px - 20px);

  word-break: break-all;

  box-shadow: 0 2px 4px rgba(0,0,0,0.12),0 0 6px rgba(0,0,0,0.04);

  border: 2px solid #eee;
  padding: 10px 20px;

  overflow-y: auto;
}

.save-button {
  margin: 0 auto;
  display: block;
}
</style>

In the processing logic, vue3's setup api is used to define state Path and state Content is two key responsive variables, which call markd, highlight, keyboardjs and other items to realize functions.

<script setup>
import { defineProps, defineEmits, reactive, watch, computed } from 'vue'
import marked from 'marked'
import "github-markdown-css/github-markdown.css"

import hljs from 'highlight.js'
import "highlight.js/scss/default.scss"

import keyboard from "keyboardjs"

marked.setOptions({
  renderer: new marked.Renderer(),
  highlight: function(code, lang) {
    const language = hljs.getLanguage(lang) ? lang : 'plaintext';
    return hljs.highlight(code, { language }).value;
  },
  pedantic: false,
  gfm: true,
  breaks: false,
  sanitize: false,
  smartLists: true,
  smartypants: false,
  xhtml: false
});

const props = defineProps({
  content: String,
  path: String,
})

const state = reactive({ content: props.content })

watch(() => props.content, () => {
  state.content = props.content
})

watch(() => props.path, () => {
  state.path = props.path
})

const renderedContent = computed(() => {
  return marked(state.content)
})


// save and save as
const emits = defineEmits(['save'])

const saveText = "Preserve( " + (utools.isMacOs() ? "⌘" : 'Ctrl') + " + S )"
const saveAsText = "Save as( " + (utools.isMacOs() ? "⌘" : 'Ctrl') + " + Shift + S )"

function handleSave() {
  if (props.path === "") {
    handleSaveAs()
  } else {
    emits('save', props.path, state.content);
  }
}

function handleSaveAs() {
  const savePath = utools.showSaveDialog({
    title: 'Save location',
    defaultPath: "Temporary documents.md",
    buttonLabel: 'preservation'
  })
  if (savePath) {
    emits('save', savePath, state.content);
  }
}

// keyboard
keyboard.bind("mod > s", () => {
  handleSave()
});
keyboard.bind("mod + shift > s", () => {
  handleSaveAs()
});

</script>

Call utools capability to save

preload.js

The previous section mentioned preload JS can realize some client functions that cannot be realized by the web, such as reading and saving client files. According to official regulations, plug-ins that need to be put on the shelf and plug-ins in the market need to be preloaded in clear text JS for audit. The core capability to be used here is to read and save user files. Here, the reading and saving are also closed to reduce the possibility that business layer bugs penetrate the past and affect the normal operation of the system.

const fs = require('fs');

console.log("preload js loaded")

window.readMarkdownFile = function (path) {
  if (path.match(/\.md$/i)) {
    return fs.readFileSync(path, {
      encoding: "utf-8"
    });
  } else {
    return "";
  }
}

window.writeMarkdownFile = function (path, content) {
  if (fs.existsSync(path)) {
    if (path.match(/\.md$/i)) {
      fs.writeFileSync(path, content)
      return true;
    } else {
      return false;
    }
  } else {
    fs.writeFileSync(path, content)
    return true;
  }
}

App.vue

In the business layer, you can call the readMarkdownFile and writeMarkdownFile methods exposed through the window variable, and use the hook function of utools( onPluginEnter )You can identify whether the portal has copied the markdown file or opened it directly.

<template>
  <Editor :content="state.content" :path="state.path" @save="handleSave" />
</template>

<script setup>
import {reactive} from 'vue';
import { ElMessage } from 'element-plus'
import Editor from './components/Editor.vue'

const state = reactive({
  content: "",
  path: "",
})

function handleSave(path, content) {
  if (path && content !== state.content) {
    writeMarkdownFile(path, content)
    ElMessage.success({
      message: 'Saved successfully',
      type: 'success'
    });

    if (state.path === '') {
      state.path = path
      state.content = readMarkdownFile(state.path)
    }
  }
}

utools.onPluginEnter(({code, type, payload}) => {
  console.log('User access plug-in', code, type, payload)

  if (type === 'files') {
    state.path = payload[0].path;
    state.content = readMarkdownFile(state.path)
  } else {
    state.path = ""
    state.content = ""
  }
})
</script>

<style>
</style>

Package upx or publish to plug-in Center

After debugging, click package as upx to install, test or distribute by yourself

You can also click publish plug-ins in the plug-in publishing to fill in relevant information. After approval, you can see the published plug-ins in the plug-in market.

You can also click publish plug-ins in the plug-in publishing to fill in relevant information. After approval, you can see the published plug-ins in the plug-in market.

FAQs and summary

Whether you use webpack or vite, you must pay attention to the packaging path (base: ". /") when packaging, because utools needs to package resources according to the relative path index, otherwise it is good in debugging mode, and there will be problems as long as it is published as upx.

The development of utools plug-ins is also very fast. After the basic framework of the plug-ins corresponding to this blog is introduced, the development, debugging and documentation time is less than six hours.

Finally, I wish utools better and better, more and more people improve the plug-in ecology of utools, and a single spark can start a prairie fire.

Keywords: Vue Vue.js

Added by salasilm on Fri, 24 Dec 2021 10:56:16 +0200