230 lines
6.2 KiB
JavaScript
230 lines
6.2 KiB
JavaScript
import 'iconify-icon'; // import only
|
|
|
|
import $ from 'cash-dom';
|
|
import dayjs from 'dayjs';
|
|
import objectPath from 'object-path';
|
|
|
|
import { JSONEditor } from '@json-editor/json-editor/dist/jsoneditor';
|
|
|
|
import { saveCVJSON } from '../lib/store';
|
|
import {
|
|
createElement,
|
|
downloadContent,
|
|
downloadIframeHTML,
|
|
propertiesToObject,
|
|
traverseDownObject,
|
|
} from '../lib/utils';
|
|
import * as jsoncvSchemaModule from '../schema/jsoncv.schema.json';
|
|
import { registerIconLib } from './iconlib';
|
|
import { registerTheme } from './theme';
|
|
|
|
const propertiesInOrder = ['basics', 'education', 'work', 'skills', 'projects', 'sideProjects', 'languages', 'interests', 'references', 'awards', 'publications', 'volunteer', 'meta']
|
|
const basicsPropertiesInOrder = ['name', 'label', 'email', 'phone', 'url', 'summary', 'image', 'location', 'profiles']
|
|
|
|
// toc elements
|
|
const elToc = document.querySelector('.editor-toc')
|
|
const tocUl = createElement('ul', {
|
|
parent: elToc
|
|
})
|
|
const basicsUl = createElement('ul', {
|
|
parent: tocUl
|
|
})
|
|
|
|
|
|
// copy the object to remove the readonly restriction on module
|
|
const jsoncvSchema = {...jsoncvSchemaModule.default}
|
|
|
|
// add propertyOrder to schema, and add links to toc
|
|
propertiesInOrder.forEach((name, index) => {
|
|
jsoncvSchema.properties[name].propertyOrder = index
|
|
|
|
const li = createElement('li', {parent: tocUl})
|
|
createElement('a', {
|
|
text: name,
|
|
attrs: {
|
|
href: `#root.${name}`
|
|
},
|
|
parent: li,
|
|
})
|
|
|
|
if (name === 'basics') {
|
|
li.appendChild(basicsUl)
|
|
}
|
|
})
|
|
basicsPropertiesInOrder.forEach((name, index) => {
|
|
jsoncvSchema.properties.basics.properties[name].propertyOrder = index
|
|
// only add location and profiles to basics toc
|
|
if (!['location', 'profiles'].includes(name)) return
|
|
const li = createElement('li', {parent: basicsUl})
|
|
createElement('a', {
|
|
text: name,
|
|
attrs: {
|
|
href: `#root.basics.${name}`
|
|
},
|
|
parent: li,
|
|
})
|
|
})
|
|
|
|
// add headerTemplate for each type:array in schema
|
|
traverseDownObject(jsoncvSchema, (key, obj) => {
|
|
let noun = key
|
|
if (noun.endsWith('s')) noun = noun.slice(0, -1)
|
|
if (obj.type === 'array' && obj.items) {
|
|
obj.items.headerTemplate = `${noun} {{i1}}`
|
|
}
|
|
})
|
|
|
|
// add format to schema
|
|
const keyFormatMap = {
|
|
'basics.properties.summary': 'textarea',
|
|
'work.items.properties.description': 'textarea',
|
|
'work.items.properties.summary': 'textarea',
|
|
'work.items.properties.highlights.items': 'textarea',
|
|
'projects.items.properties.description': 'textarea',
|
|
'projects.items.properties.highlights.items': 'textarea',
|
|
'sideProjects.items.properties.description': 'textarea',
|
|
'references.items.properties.reference': 'textarea',
|
|
'awards.items.properties.summary': 'textarea',
|
|
'publications.items.properties.summary': 'textarea',
|
|
'volunteer.items.properties.summary': 'textarea',
|
|
'volunteer.items.properties.highlights.items': 'textarea',
|
|
}
|
|
for (const [key, format] of Object.entries(keyFormatMap)) {
|
|
objectPath.get(jsoncvSchema.properties, key).format = format
|
|
}
|
|
|
|
// change schema title
|
|
jsoncvSchema.title = 'Resume'
|
|
|
|
// change some descriptions
|
|
jsoncvSchema.properties.meta.properties.lastModified.description += '. This will be automatically updated when downloading.'
|
|
|
|
// initialize editor
|
|
registerTheme(JSONEditor)
|
|
registerIconLib(JSONEditor)
|
|
const elEditorContainer = document.querySelector('.editor-container')
|
|
const editor = new JSONEditor(elEditorContainer, {
|
|
schema: jsoncvSchema,
|
|
theme: 'mytheme',
|
|
iconlib: 'myiconlib',
|
|
disable_array_delete_all_rows: true,
|
|
no_additional_properties: true,
|
|
// startval: exampleData,
|
|
});
|
|
editor.on('ready',() => {
|
|
// editor.setValue(exampleData)
|
|
|
|
// add anchor to each schema element
|
|
document.querySelectorAll('[data-schemapath]').forEach(el => {
|
|
const schemapath = el.getAttribute('data-schemapath')
|
|
el.id = schemapath
|
|
})
|
|
})
|
|
|
|
function getCVData() {
|
|
const data = editor.getValue()
|
|
return {
|
|
data,
|
|
json: JSON.stringify(data, null, 2),
|
|
}
|
|
}
|
|
|
|
const $outputJSON = $('.output-json')
|
|
const $outputHTML = $('.output-html')
|
|
|
|
// listen to change
|
|
editor.on('change', () => {
|
|
console.log('on editor change')
|
|
const {json} = getCVData()
|
|
$outputJSON.text(json)
|
|
|
|
// save to localstorage
|
|
saveCVJSON(json)
|
|
})
|
|
|
|
// actions
|
|
const $btnShowPreview = $('#fn-show-preview')
|
|
const $btnShowJSON = $('#fn-show-json')
|
|
const $btnNewData = $('#fn-new-data')
|
|
const $btnUploadData = $('#fn-upload-data')
|
|
const $inputUploadData = $('input[name=upload-data]')
|
|
const $btnDownloadJSON = $('#fn-download-json')
|
|
const $btnDownloadHTML = $('#fn-download-html')
|
|
|
|
$btnShowPreview.on('click', () => {
|
|
$outputJSON.hide()
|
|
$outputHTML.show()
|
|
})
|
|
|
|
$btnShowJSON.on('click', () => {
|
|
$outputHTML.hide()
|
|
$outputJSON.show()
|
|
})
|
|
|
|
$btnNewData.on('click', () => {
|
|
if (!confirm('Are you sure to create an empty CV? Your current data will be lost.')) return
|
|
|
|
const v = propertiesToObject(jsoncvSchema.properties)
|
|
console.log('new value', v)
|
|
editor.setValue(v)
|
|
})
|
|
|
|
$btnUploadData.on('click', () => {
|
|
if (!confirm('Are you sure to upload an existing CV data? Your current data will be covered.')) return
|
|
$inputUploadData.trigger('click')
|
|
})
|
|
|
|
$inputUploadData.on('change', () => {
|
|
const files = $inputUploadData.get(0).files
|
|
if (files.length === 0) return
|
|
|
|
const reader = new FileReader()
|
|
reader.onload = (e) => {
|
|
let data
|
|
try {
|
|
data = JSON.parse(e.target.result)
|
|
} catch (e) {
|
|
const error = 'Invalid JSON file: ' + new String(e).toString()
|
|
console.log(error)
|
|
throw e
|
|
}
|
|
editor.setValue(data)
|
|
}
|
|
|
|
reader.readAsText(files[0])
|
|
})
|
|
|
|
function downloadCV(contentType) {
|
|
const data = editor.getValue()
|
|
const meta = data.meta || (data.meta = {})
|
|
let name = meta.name
|
|
if (!name) {
|
|
name = prompt(`Please enter a name for your CV's data`)
|
|
}
|
|
if (!name) return
|
|
|
|
// update data
|
|
meta.name = name
|
|
meta.lastModified = dayjs().format('YYYY-MM-DDTHH:mm:ssZ[Z]')
|
|
|
|
// download
|
|
if (contentType === 'json') {
|
|
let filename = `${name}.json`
|
|
downloadContent(filename, JSON.stringify(data, null, 2))
|
|
} else if (contentType === 'html') {
|
|
let filename = `${name}.html`
|
|
downloadIframeHTML(filename, $outputHTML.get(0))
|
|
}
|
|
|
|
// update editor value
|
|
editor.getEditor('root.meta').setValue(meta)
|
|
}
|
|
|
|
$btnDownloadJSON.on('click', () => {
|
|
downloadCV('json')
|
|
})
|
|
|
|
$btnDownloadHTML.on('click', () => {
|
|
downloadCV('html')
|
|
})
|