preface

Half a month ago, I wrote an article about how to write a good Internet college recruitment resume, the purpose is to help students who are about to start to send out college recruitment better improve their resume

A Flag was also set in the article

I have looked at the Commit record of Github. Up to now, I have spent about a week to work out the plan I had in mind. Maybe it is not perfect, but I think it can also help some students

Good things are shown three times, of course

  • Experience the link
  • Experience the link
  • Experience the link

If you are not satisfied with the template style (color, typesetting), students who understand front-end magic can clone the warehouse and display their magic beautification

If you are interested in the project, you are welcome to contribute your favorite resume template (code). Theoretically, there is no limit to the development technology stack. Of course, you are welcome to raise issues or suggestions

This paper mainly describes the design ideas, technical solutions, some problems encountered and solutions (using a lot of hack skills) of this project, as well as the subsequent planning

The project design

layout

The basic page structure of the entire application

<body>
    <header>
        <! Navigation -- - >
        <nav></nav>
    </header>
    <div>
        <! -- Show your resume -->
        <iframe></iframe>
        <! -- Control area -->
        <div></div>
    </div>
</body>
Copy the code

Some of your friends here are wondering why you use an iframe?

Here to give you a brief introduction, later in the technical solutions will be explained to you

In my scenario, the resume section has only presentation logic and can be seen as a stand-alone, purely static page

Since it’s just a presentation, any front-end magic can do the job, so to make it easier for the various magicians to do the work, it’s a separate piece, and the contributors are only concerned with restoring a static page, leaving the rest of the interactive logic to the parent page

Technology selection

Vanilla JS – the world’s lightest JavaScript framework (no one) —- Native JS

The main part of the entire application is implemented in native JS

The presentation part of the resume can theoretically be implemented with any front-end technology stack, low coupling with the parent page

communication

  • Use the navigation bar to switch between different resume templates
  • Changes on the resume are automatically synchronized to the page description in the control area
  • If the page description is changed in the control area, the resume is updated in real time

Describe your resume

  • Use JSON to describe the structure and content of the resume
  • One template corresponds to one JSON

Page Description Displays the information

  • Use JSON to describe various information on your resume
  • Provides a JSON editor
  • In this case, the JSON editor is jsonEditor

Data access

  • The whole data flow is one-way, external is responsible for update, internal (resume presentation section) is only responsible for reading
  • Data is stored locally, so there is no fear of personal information leakage
  • Here thelocalStorage

First edition effect

Here are the key parts of the project implementation

implementation

Project directory structure

/config Webpack configuration file.webpack.base.js -- Public configuration.webpack.config.build.js -- Special configuration for the production environment.webpack.config.dev.js -- ├── webpack.config.js ├── CSS │ ├─ print. CSS │ ├── assets ├── index.js │ ├── schema Default JSON data for each resume template, corresponding to the template in pages │ ├── demo1.js ├── pages │ ├─ demo1 │ ├─ utils │ ├─ utils │ ├─ app.js │ ├─ index.html │ ├─ index.htmlCopy the code

Convention over Configuration

According to the agreed directory structure, through automated scripts

All templates are stored in the SRC /pages/ XXX directory

The page template convention is index.html, and all JS files in this directory will be automatically added to the WebPack entry and automatically injected into the current page template

For example,

The. / SRC ├ ─ ─ pages │ └ ─ ─ XXX │ └ ─ ─ ─ ─ ─ index. The HTML │ └ ─ ─ ─ ─ ─ index. The SCSS │ └ ─ ─ ─ ─ ─ index. JsCopy the code

The entry/ Page configuration code that is automatically generated here can be viewed here

The automatically generated results are as follows

The content format of each HTMLWebpackPlugin is as follows

Automatically generates a navigation bar

At the top of the home page is a navigation bar for switching between the routes of the resume template

If this part of the link content is very boring to fill in manually, how to achieve automatic generation?

The header Nav section of the first page template reads

<header>
    <nav id="nav">
        <%= htmlWebpackPlugin.options.pageNames %>
    </nav>
</header>
Copy the code

HtmlWebpackPlugin. Options said htmlWebpackPlugin userOptions attributes of objects

PageNames = useroptions.pagenames = useroptions.pagenames = useroptions.pagenames = useroptions.pagenames = useroptions.pagenames = useroptions.pagenames = useroptions.pagenames = useroptions.pagenames

<header>
    <nav id="nav">
        abc,demo1,vue1,react1,introduce
    </nav>
</header>
Copy the code

With the first render results in hand, let’s write a method to turn the content into a tag

const navTitle = {
    'demo1': Template '1'.'react1': Template '2'.'vue1': Template '3'.'introduce': 'Use document'.'abc': 'Development Examples'
}

function createLink(text, href, newTab = false) {
    const a = document.createElement('a')
    a.href = href
    a.text = text
    a.target = newTab ? '_blank' : 'page'
    return a
}

/** * Initialize the navigation bar */
function initNav(defaultPage = 'react1') {
    const $nav = document.querySelector('header nav')
    // Get links to all templates -- process the original content
    const links = $nav.innerText.split(', ').map(pageName= > {
        const link = createLink(navTitle[pageName] || pageName, `./pages/${pageName}`)
        // Open in iframe
        return link
    })

    // Add a custom link
    links.push(createLink('Github'.'https://github.com/ATQQ/resume'.true))
    links.push(createLink('Contribution Template'.'https://github.com/ATQQ/resume/blob/main/README.md'.true))
    links.push(createLink('How to Write a Good Online college Recruitment Resume'.'https://juejin.cn/post/6928390537946857479'.true))
    links.push(createLink('Suggestions/feedback'.'https://www.wenjuan.com/s/MBryA3gI/'.true))

    // Render to the page
    const t = document.createDocumentFragment()
    links.forEach(link= > {
        t.appendChild(link)
    })
    $nav.innerHTML = ' '
    $nav.append(t)
}

initNav()
Copy the code

The navigation bar is “automatically” generated

Automatically export the page description

directory

The. / SRC ├ ─ ─ constants │ ├ ─ ─ index. The js │ ├ ─ ─ schema. Js │ ├ ─ ─ schema │ ├ ─ ─ ─ ─ ─ ─ not. Js │ ├ ─ ─ ─ ─ ─ ─ react1. Js │ └ ─ ─ ─ ─ ─ ─ vue1. JsCopy the code

From every page of the default data. / SRC/constants/schema. Js reads

import abc from './schema/abc'
import demo1 from './schema/demo1'
import react1 from './schema/react1'
import vue1 from './schema/vue1'

export default{
    abc,demo1,react1,vue1
}
Copy the code

The description content of each template is distributed in the Schema directory. If each developer manually adds his/her own template to schema.js, it is likely to cause conflicts, so it is simply generated automatically

For tool methods, go here

/** * Automatically create the SRC/Constants /schema.js file */
function writeSchemaJS() {
    const files = getDirFilesWithFullPath('src/constants/schema')
    const { dir } = path.parse(files[0])
    const targetFilePath = path.resolve(dir, '.. / '.'schema.js')
    const names = files.map(file= > path.parse(file).name)
    const res = `${names.map(n => {
        return `import ${n} from './schema/${n}'`
    }).join('\n')}

export default{
    ${names.join(', ')}} `
    fs.writeFileSync(targetFilePath, res)
}
Copy the code

Data access

Data access operations are used on both parent and child pages and are extracted as public methods

Data is stored in localStorage, and the route of each resume template is used as the key

./src/utils/index.js

import defaultSchema from '.. /constants/schema'

export function getSchema(key = ' ') {
    if(! key) {// The default key is a route, such as origin.com/pages/react1
        // key就为 pages/react1
        key = window.location.pathname.replace(/ / / $/.' ')}// Get it from local first
    let data = localStorage.getItem(key)
    // If not, set a default refetch
    if(! data) { setSchema(getDefaultSchema(key), key)return getSchema()
    }
    // If the default is null, take the default again
    if (data === '{}') {
        setSchema(getDefaultSchema(key), key)
        data = localStorage.getItem(key)
    }
    return JSON.parse(data)
}

export function getDefaultSchema(key) {
    const _key = key.slice(key.lastIndexOf('/') + 1)
    return defaultSchema[_key] || {}
}

export function setSchema(data, key = ' ') {
    if(! key) { key =window.location.pathname.replace(/ / / $/.' ')}localStorage.setItem(key, JSON.stringify(data))
}
Copy the code

Json description display

You need to display the description information of JSON in the control area. The display part uses jsonEditor

Of course, JsonEditor also supports all kinds of data manipulation (CRUD) and provides shortcut action buttons

Here the CDN method is used to introduce jsonEditor

<link rel="stylesheet" href="https://img.cdn.sugarat.top/css/jsoneditor.min.css">
<script src="https://img.cdn.sugarat.top/js/jsoneditor.min.js"></script>
Copy the code

Initialize the

/** * Initialize the JSON editor@param {string} id 
 */
function initEditor(id) {
    let timer = null
    // Here is a simple shockproof
    const editor = new JSONEditor(document.getElementById(id), {
        // Trigger when the json content changes
        onChangeJSON(data) {
            if (timer) {
                clearTimeout(timer)
            }
            The updatePage method is used to notify child pages of updates
            setTimeout(updatePage, 200, data)
        }
    })
    return editor
}

const editor = initEditor('jsonEditor')
Copy the code

Display effect

Json data display/update timing

  • The onLoad event of the iframe is triggered every time the route is switched
  • This is where you put the time to get the content of the editor update JSON
function getPageKey() {
    return document.getElementById('page').contentWindow.location.pathname.replace(/ / / $/.' ')}document.getElementById('page').onload = function (e) {
    // Update what is displayed in the editor
    editor.set(getSchema(getPageKey()))
}
Copy the code

Writing a template page

Here are four ways to implement the same page

Desired effect

Description file

Create a JSON description file for the page in the Schema directory, such as abc.js

./ SRC Constants │ ├── schema │ ├── ab.jsCopy the code

abc.js

export default {
    name: 'Cathy'.position: 'Job Objective: Web Front-end Engineer'.infos: [
        '1: a lot of text '.'2: A lot of text '.'3: A lot of text ']},Copy the code

The desired render structure

<div id="resume">
    <div id="app">
        <header>
            <h1>Cathy</h1>
            <h2>Job objective: Web front-end engineer</h2>
        </header>
        <ul class="infos">
            <li>1: Lots of words<li>
            <li>2: Lots of words<li>
            <li>3: Lots of text<li>
        </ul>
    </div>
</div>
Copy the code

Let’s start subcoding

The only logic related to the parent page is the need to mount a refresh method on the child page’s Window for the parent page to proactively call updates

Native js

import { getSchema } from ".. /.. /utils"

window.refresh = function () {
    const schema = getSchema()
    const { name, position, infos } = schema
    / /... Render logic
}
Copy the code

vue

<script> import { getSchema } from '.. /.. /utils'; export default { data() { return { schema: getSchema(), }; }, mounted() { window.refresh = this.refresh; }, methods: { refresh() { this.schema = getSchema(); ,}}}; </script>Copy the code

react

import React, { useEffect, useState } from 'react'
import { getSchema } from '.. /.. /utils'

export default function App() {
    const [schema, updateSchema] = useState(getSchema())
    const { name, position, infos = [] } = schema
    useEffect(() = > {
        window.refresh = function () {
            updateSchema(getSchema())
        }
    }, [])
    return (
        <div>{/* render dom logic */}</div>)}Copy the code

The code is folded for easy reading

The first is the style, sASS preprocessor language is chosen here, but native CSS is also available

index.scss
@import '. /.. /.. /assets/css/base.scss';
html.body.#resume {
  height: 100%;
  overflow: hidden;
}
// The above section is recommended to introduce common styles

// Let's write our style
$themeColor: red;

#app {
  padding: 1rem;
}

header {
  h1 {
    color: $themeColor;
  }
  h2 {
    font-weight: lighter; }}.infos {
  list-style: none;
  li {
    color: $themeColor; }}Copy the code

The second is the page description file

index.html
<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>
        <%= htmlWebpackPlugin.options.title %>
    </title>
</head>

<body>
    <div id="resume">
        <div id="app">

        </div>
    </div>
</body>

</html>
Copy the code

Let’s start writing logical code using various technology stacks

Native js

The directory structure

The. / SRC ├ ─ ─ pages │ └ ─ ─ ABC │ └ ─ ─ ─ ─ ─ index. The HTML │ └ ─ ─ ─ ─ ─ index. The SCSS │ └ ─ ─ ─ ─ ─ index. JsCopy the code

index.js

import { getSchema } from ".. /.. /utils"
import './index.scss'

window.refresh = function () {
    const schema = getSchema()
    const { name, position, infos } = schema

    clearPage()
    renderHeader(name, position)
    renderInfos(infos)
}

function clearPage() {
    document.getElementById('app').innerHTML = ' '
}

function renderHeader(name, position) {
    const html = `
    <header>
        <h1>${name}</h1>
        <h2>${position}</h2>
    </header>`
    document.getElementById('app').innerHTML += html
}

function renderInfos(infos = []) {
    if(infos? .length ===0) {
        return
    }
    const html = `
    <ul class="infos">
    ${infos.map(info => {
        return `<li>${info}</li>`
    }).join(' ')}
    </ul>`
    document.getElementById('app').innerHTML += html
}

window.onload = function () {
    refresh()
}
Copy the code
Vue

The directory structure

The. / SRC ├ ─ ─ pages │ └ ─ ─ ABC │ └ ─ ─ ─ ─ ─ index. The HTML │ └ ─ ─ ─ ─ ─ index. The SCSS │ └ ─ ─ ─ ─ ─ index. The js │ └ ─ ─ ─ ─ ─ App. VueCopy the code

index.js

import Vue from 'vue'
import App from './App.vue'
import './index.scss'

Vue.config.productionTip = process.env.NODE_ENV === 'development'

new Vue({
    render: h= > h(App)
}).$mount('#app')
Copy the code

App.vue

<template> <div id="app"> <header> <h1>{{ schema.name }}</h1> <h2>{{ schema.position }}</h2> </header> <div class="infos"> <p v-for="(info, i) in schema.infos" :key="i" > {{ info }} </p> </div> </div> </template> <script> import { getSchema } from '.. /.. /utils'; export default { data() { return { schema: getSchema(), }; }, mounted() { window.refresh = this.refresh; }, methods: { refresh() { this.schema = getSchema(); ,}}}; </script>Copy the code
React

The directory structure

The. / SRC ├ ─ ─ pages │ └ ─ ─ ABC │ └ ─ ─ ─ ─ ─ index. The HTML │ └ ─ ─ ─ ─ ─ index. The SCSS │ └ ─ ─ ─ ─ ─ index. The js │ └ ─ ─ ─ ─ ─ App. JSXCopy the code

index.js

import React from 'react'
import ReactDOM from 'react-dom';
import App from './App.jsx'
import './index.scss'

ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>.document.getElementById('app'))Copy the code

App.jsx

import React, { useEffect, useState } from 'react'
import { getSchema } from '.. /.. /utils'

export default function App() {
    const [schema, updateSchema] = useState(getSchema())
    const { name, position, infos = [] } = schema
    useEffect(() = > {
        window.refresh = function () {
            updateSchema(getSchema())
        }
    }, [])
    return (
        <div>
            <header>
                <h1>{name}</h1>
                <h2>{position}</h2>
            </header>
            <div className="infos">
                {
                    infos.map((info, i) => {
                        return <p key={i}>{info}</p>})}</div>
        </div>)}Copy the code
jQuery

The directory structure

The. / SRC ├ ─ ─ pages │ └ ─ ─ ABC │ └ ─ ─ ─ ─ ─ index. The HTML │ └ ─ ─ ─ ─ ─ index. The SCSS │ └ ─ ─ ─ ─ ─ index. JsCopy the code

index.js

import { getSchema } from ".. /.. /utils"
import './index.scss'

window.refresh = function () {
    const schema = getSchema()
    const { name, position, infos } = schema

    clearPage()
    renderHeader(name, position)
    renderInfos(infos)
}

function clearPage() {$('#app').empty()
}

function renderHeader(name, position) {
    const html = `
    <header>
        <h1>${name}</h1>
        <h2>${position}</h2>
    </header>`
    $('#app').append(html)
}

function renderInfos(infos = []) {
    if(infos? .length ===0) {
        return
    }
    const html = `
    <ul class="infos">
    ${infos.map(info => {
        return `<li>${info}</li>`
    }).join(' ')}
    </ul>`
    $('#app').append(html)
}

window.onload = function () {
    refresh()
}
Copy the code

If you find the navigation bar showing ABC unfriendly, you can change it, of course

/ SRC Constants │ ├─ index. JsCopy the code

Add aliases to./ SRC /constants/index.js

export const navTitle = {
    'abc': 'Development Examples'
}
Copy the code

Subpage update

There was an updatePage method earlier when you instantiated the Editor

If the child page has the refresh method, it is directly called to update the page, of course, before the update the parent page will save the latest data to the localStorage

In this way, there is no direct exchange of data between the pages, one is responsible for writing, one is responsible for reading, even if the write failure does not affect the child page to read the original data

function refreshIframePage(isReload = false) {
    const page = document.getElementById('page')
    if (isReload) {
        page.contentWindow.location.reload()
        return
    }
    if (page.contentWindow.refresh) {
        page.contentWindow.refresh()
        return
    }
    page.contentWindow.location.reload()
}

function updatePage(data) {
    setSchema(data, getPageKey())
    refreshIframePage()
}

/** * Initialize the JSON editor@param {string} id 
 */
function initEditor(id) {
    let timer = null
    // Here is a simple shockproof
    const editor = new JSONEditor(document.getElementById(id), {
        // Trigger when the json content changes
        onChangeJSON(data) {
            if (timer) {
                clearTimeout(timer)
            }
            The updatePage method is used to notify child pages of updates
            setTimeout(updatePage, 200, data)
        }
    })
    return editor
}

const editor = initEditor('jsonEditor')
Copy the code

Export PDF

PC

Firstly, PC browser supports printing and exporting PDF

How do I trigger the print?

  • Right-click and choose Print
  • Shortcut key Ctrl + P
  • window.print()

Let’s use the third option in our code here

How do I make sure that only the resume is printed?

This is where you use media queries

Methods a

@media print {
    /* This part of the written style is still valid at printing */
}
Copy the code

Way 2

<! -- Imported CSS resources only take effect at print time -->
<link rel="stylesheet" href="./css/print.css" media="print">
Copy the code

Just hide the irrelevant content in the print style

You can basically do a 1:1 reduction

The mobile terminal

Using jsPDF + HTML2Canvas

  1. Html2canvas is responsible for turning pages into images
  2. JsPDF is responsible for converting the images to PDF
function getBase64Image(img) {
    var canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0.0, img.width, img.height);
    var dataURL = canvas.toDataURL("image/png");
    return dataURL;
}
/ / export PDF
// Make sure the image resource is base64, otherwise the exported resume will not show the image
html2canvas(document.getElementById('page').contentDocument.body).then(canvas= > {
    // Return the image dataURL with parameters: image format and definition (0-1)
    var pageData = canvas.toDataURL('image/jpeg'.1.0);
    // The default direction is vertical, size ponits, format A4 [595.28,841.89]
    var doc = new jsPDF(' '.'pt'.'a4');
    //addImage the last two parameters control the size of the image to be added. Here, the page height is compressed according to the width and height ratio of A4 paper
    // doc.addimage (pageData, 'JPEG', 0, 0, 595.28, 592.28 / Canvas.width * canvas.height);
    doc.addImage(pageData, 'JPEG'.0.0.595.28.841.89);
    doc.save(`The ${Date.now()}.pdf`);
});
Copy the code

However, at present, there are still some problems in this way of export, which will be dealt with by other schemes later

  1. Hyperlinks are not supported
  2. Does not support iconfont
  3. The white space of the font is removed

summary

And here we have the prototype of the whole project

  • Navigation bar switch resume template
  • Change in the JSON editorjson-> Update page data
  • Export PDF
    • Mobile – JSPDF
    • Computer-print

High energy operation

Highlight changes

Claim: Updated content in json editor to highlight changes in resume

Turning into a technical requirement is to expect to be able to listen to the changing DOM and highlight it

This is where the MutationObserver comes in

It provides the ability to monitor changes made to the DOM tree

/** * Highlight changes in Dom */
function initObserver() {
    // Contains the descendant node
    // Extend the monitoring scope to all nodes in the target node's entire node tree
    // Monitor changes in character data contained by nodes in the specified target node or child node tree
    const config = { childList: true.subtree: true.characterData: true };

    // Instantiate the listener object
    const observer = new MutationObserver(debounce(function (mutationsList, observer) {
        for (const e of mutationsList) {
            let target = e.target
            if (e.type === 'characterData') {
                target = e.target.parentElement
            }
            / / highlight
            highLightDom(target)
        }
    }, 100))
    // Listen for the body of the child page
    observer.observe(document.getElementById('page').contentDocument.body, config);
    // Because MutationObserver is a microtask, the microtask is followed by page rendering
    
    // Stop observing changes
    // The macro task is used to ensure that the Event loop ends
    setTimeout(() = > {
        observer.disconnect()
    }, 0)}function highLightDom(dom, time = 500, color = '#fff566') {
    if(! dom? .style)return
    if (time === 0) {
        dom.style.backgroundColor = ' '
        return
    }
    dom.style.backgroundColor = '#fff566'
    setTimeout(() = > {
        dom.style.backgroundColor = ' '
    }, time)
}
Copy the code

When initObserver is called

Of course, register the event before updating the page, and stop listening when the page has completed the change rendering

function updatePage(data) {
    // For an asynchronous microtask, the current event loop ends and the observation stops
    initObserver()
    / / synchronize
    setSchema(data, getPageKey())
    // Sync + render page
    refreshIframePage()
}
Copy the code

The effect

Which change which point

Expect effect

Appeal:

  • Click the part you want to modify, and you can modify it
  • The results are synchronized on the resume with the CONTENT in the JSON editor

The implementation is described below

1. Get the Dom of the click

document.getElementById('page').contentDocument.body.addEventListener('click'.function (e) {
    const $target = e.target
})
Copy the code

2. Obtain the number and relative position of dom content in the page

  1. The child page only contains the display logic, so the parent page needs to hack to locate the corresponding location of the content in the JSON
  2. There is more than one DOM that has the same content, so you need to find them all
/** * Traverse the target Dom tree to find the Dom group whose text content is consistent with the target */
function traverseDomTreeMatchStr(dom, str, res = []) {
    // If there are children, then continue to iterate over them
    if(dom? .children? .length >0) {
        for (const d of dom.children) {
            traverseDomTreeMatchStr(d, str, res)
        }
        // If it is equal, record it
    } else if(dom? .textContent? .trim() === str) { res.push(dom) }return res
}

// Monitor the resume page for click events
document.getElementById('page').contentDocument.body.addEventListener('click'.function (e) {
    const $target = e.target
    // Click on the content
    const clickText = $target.textContent.trim()
    // A node containing only clicked content
    const matchDoms = traverseDomTreeMatchStr(document.getElementById('page').contentDocument.body, clickText)
    // The relative position of the clicked node in the matching node
    const mathIndex = matchDoms.findIndex(v= > v === $target)
    // If no, no processing will be performed
    if (mathIndex < 0) {
        return}})Copy the code

3. Get the corresponding node in the JsonEditor

  • Similar logic to above
  • Start by filtering out a few nodes that contain only the contents of this node
  • It is then matched based on the relative position of the clicked DOM in the list of content nodes
// Monitor the resume page for click events
document.getElementById('page').contentDocument.body.addEventListener('click'.function (e) {
    / /... Omit the code listed above

    // Unhighlight the dom from the last click
    highLightDom($textarea.clickDom, 0)
    // Highlight 10s this time
    highLightDom($target, 10000)


    // Update the search content in the JsonEditor
    editor.searchBox.dom.search.value = clickText
    // Trigger the search actively
    editor.searchBox.dom.search.dispatchEvent(new Event('change'))

    // Display the clicked content in the Textarea
    $textarea.value = clickText
    
    // Autofocus the input box
    if (document.getElementById('focus').checked) {
        $textarea.focus()
    }

    // Record the dom clicked and mount the $textarea
    $textarea.clickDom = e.target

    // JsonEditor searches for fuzzy matches, such as ba,baba,a,aa,aaa
    // Match exactly the same JSON nodes according to the matchIndex
    let i = -1
    for (const r of editor.searchBox.results) {
        // The subscript is changed only when the subscript is equal
        if (r.node.value === clickText) {
            i++
            // Match the node in json
            if (i === mathIndex) {
                // Highlight $textarea
                $textarea.style.boxShadow = '0 0 1rem yellow'
                setTimeout(() = > {
                    $textarea.style.boxShadow = ' '
                }, 200)
                return}}// Manually trigger the Next Search Match button in jsonEditor to switch the active node in JsonEditor
        editor.searchBox.dom.input.querySelector('.jsoneditor-next').dispatchEvent(new Event('click'))
        // The active node can be obtained in the following ways
        // editor.searchBox.activeResult.node}})Copy the code

4. Update the node content

  1. The above two steps capture both the DOM of the resume and the DOM of the JsonEditor
  2. The content entered through the textarea
  3. Update the input to each DOM, and write the latest JSON to the localStorage
// Listen for input events and do a simple stabilization
 $textarea.addEventListener('input', debounce(function () {
    if(! editor.searchBox? .activeResult? .node) {return
    }
    // Activate the DOM change event
    initObserver()

    // Update the DOM
    $textarea.clickDom.textContent = this.value

    // Update the EDITOR's DOM
    editor.searchBox.activeResult.node.value = this.value
    editor.refresh()

    // Update to local
    setSchema(editor.get(), getPageKey())

}, 100))
Copy the code

This completes the update of the data on both sides (resume/JsonEditor)

Subsequent planning

  1. Add more framework support
  2. Optimize PDF export
    1. hyperlinks
    2. The fonts icon
  3. Optimize user experience
    1. Reduce the presence of JsonEditor, the current add and delete operations rely on JsonEditor, not friendly to students who do not understand front-end magic
    2. Optimize mobile interactions
    3. Beautify the interface
  4. Added automatic code template generation instruction
  5. Move more resume templates

Thank you so much for reading this, thank you so much for reading this, and if you’re interested, please contribute code.

A link to the

  • Online Experience Links
  • Code warehouse
  • Contribute code

recruitment

The spring 21 college enrollment of Meituan has opened, welcome to send in

The author is in the business group of the store – platform technology department. Welcome to join us