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 the
localStorage
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
- Html2canvas is responsible for turning pages into images
- 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
- Hyperlinks are not supported
- Does not support iconfont
- 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 editor
json
-> 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
- 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
- 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
- The above two steps capture both the DOM of the resume and the DOM of the JsonEditor
- The content entered through the textarea
- 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
- Add more framework support
- Optimize PDF export
- hyperlinks
- The fonts icon
- Optimize user experience
- 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
- Optimize mobile interactions
- Beautify the interface
- Added automatic code template generation instruction
- 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