add color-picker in editor and support primary color in CV HTML
This commit is contained in:
parent
4e2c1f0eca
commit
51fd7f8754
|
|
@ -16,6 +16,12 @@
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/src/scss/cv-base.css">
|
<link rel="stylesheet" href="/src/scss/cv-base.css">
|
||||||
<link rel="stylesheet" href="/src/themes/<%= theme %>/index.scss">
|
<link rel="stylesheet" href="/src/themes/<%= theme %>/index.scss">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
/* override primary color */
|
||||||
|
<%= primaryColor.var %>: <%= primaryColor.value %>;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="cv-container">
|
<div class="cv-container">
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-toc"></div>
|
<div class="editor-toc"></div>
|
||||||
<div class="app-actions">
|
<div class="app-actions">
|
||||||
|
<label for="fn-color-picker" class="color-picker">
|
||||||
|
<div>Primary color</div>
|
||||||
|
<div class="color-area">
|
||||||
|
<input type="color" id="fn-color-picker">
|
||||||
|
<span class="value">#aaaaaa</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
<button id="fn-toggle-preview">Preview/JSON</button>
|
<button id="fn-toggle-preview">Preview/JSON</button>
|
||||||
<button id="fn-download-json">Download JSON</button>
|
<button id="fn-download-json">Download JSON</button>
|
||||||
<button id="fn-download-html">Download HTML</button>
|
<button id="fn-download-html">Download HTML</button>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ import * as sampleModule from '../../sample.cv.json';
|
||||||
import * as jsoncvSchemaModule from '../../schema/jsoncv.schema.json';
|
import * as jsoncvSchemaModule from '../../schema/jsoncv.schema.json';
|
||||||
import {
|
import {
|
||||||
getCVData,
|
getCVData,
|
||||||
|
getPrimaryColor,
|
||||||
saveCVJSON,
|
saveCVJSON,
|
||||||
|
savePrimaryColor,
|
||||||
} from '../lib/store';
|
} from '../lib/store';
|
||||||
import {
|
import {
|
||||||
createElement,
|
createElement,
|
||||||
|
|
@ -140,6 +142,7 @@ function getEditorData() {
|
||||||
|
|
||||||
const $outputJSON = $('.output-json')
|
const $outputJSON = $('.output-json')
|
||||||
const $outputHTML = $('.output-html')
|
const $outputHTML = $('.output-html')
|
||||||
|
const outputHTMLIframe = $outputHTML.get(0)
|
||||||
|
|
||||||
// listen to change
|
// listen to change
|
||||||
editor.on('change', () => {
|
editor.on('change', () => {
|
||||||
|
|
@ -160,11 +163,13 @@ const $btnDownloadJSON = $('#fn-download-json')
|
||||||
const $btnDownloadHTML = $('#fn-download-html')
|
const $btnDownloadHTML = $('#fn-download-html')
|
||||||
const $btnLoadSample = $('#fn-load-sample')
|
const $btnLoadSample = $('#fn-load-sample')
|
||||||
const $btnPrintPreview = $('#fn-print-preview')
|
const $btnPrintPreview = $('#fn-print-preview')
|
||||||
|
const $inputColorPicker = $('#fn-color-picker')
|
||||||
|
const $colorValue = $('.color-picker .value')
|
||||||
|
|
||||||
const isElementHidden = elt =>
|
const isElementHidden = elt =>
|
||||||
! (elt.offsetWidth || elt.offsetHeight || elt.getClientRects().length);
|
! (elt.offsetWidth || elt.offsetHeight || elt.getClientRects().length);
|
||||||
$btnTogglePreview.on('click', () => {
|
$btnTogglePreview.on('click', () => {
|
||||||
if (isElementHidden($outputHTML.get(0))) {
|
if (isElementHidden(outputHTMLIframe)) {
|
||||||
$outputJSON.hide()
|
$outputJSON.hide()
|
||||||
$outputHTML.show()
|
$outputHTML.show()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -220,7 +225,7 @@ function downloadCV(contentType) {
|
||||||
downloadContent(filename, JSON.stringify(data, null, 2))
|
downloadContent(filename, JSON.stringify(data, null, 2))
|
||||||
} else if (contentType === 'html') {
|
} else if (contentType === 'html') {
|
||||||
let filename = `${title}.html`
|
let filename = `${title}.html`
|
||||||
downloadIframeHTML(filename, $outputHTML.get(0))
|
downloadIframeHTML(filename, outputHTMLIframe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update editor value
|
// update editor value
|
||||||
|
|
@ -242,5 +247,19 @@ $btnLoadSample.on('click', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
$btnPrintPreview.on('click', () => {
|
$btnPrintPreview.on('click', () => {
|
||||||
$outputHTML.get(0).contentWindow.print()
|
outputHTMLIframe.contentWindow.print()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// primary color
|
||||||
|
|
||||||
|
$inputColorPicker.on('change', (e) => {
|
||||||
|
const color = e.target.value
|
||||||
|
console.log('color', color)
|
||||||
|
$colorValue.text(color)
|
||||||
|
savePrimaryColor(color)
|
||||||
|
})
|
||||||
|
|
||||||
|
const primaryColor = getPrimaryColor()
|
||||||
|
$colorValue.text(primaryColor)
|
||||||
|
$inputColorPicker.val(primaryColor)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
export const storeKeys = {
|
export const storeKeys = {
|
||||||
cvJSON: 'cvJSON',
|
cvJSON: 'cvJSON',
|
||||||
cvSavedTime: 'cvSavedTime',
|
cvSavedTime: 'cvSavedTime',
|
||||||
|
primaryColor: 'primary-color',
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPrimaryColor = '#2A3FFB'
|
||||||
|
|
||||||
|
function updateSavedTime() {
|
||||||
|
localStorage.setItem(storeKeys.cvSavedTime, Date.now())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveCVJSON(str) {
|
export function saveCVJSON(str) {
|
||||||
localStorage.setItem(storeKeys.cvJSON, str)
|
localStorage.setItem(storeKeys.cvJSON, str)
|
||||||
localStorage.setItem(storeKeys.cvSavedTime, Date.now())
|
updateSavedTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCVData() {
|
export function getCVData() {
|
||||||
|
|
@ -17,3 +24,12 @@ export function getCVData() {
|
||||||
export function getCVSavedTime() {
|
export function getCVSavedTime() {
|
||||||
return localStorage.getItem(storeKeys.cvSavedTime)
|
return localStorage.getItem(storeKeys.cvSavedTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function savePrimaryColor(color) {
|
||||||
|
localStorage.setItem(storeKeys.primaryColor, color)
|
||||||
|
updateSavedTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPrimaryColor() {
|
||||||
|
return localStorage.getItem(storeKeys.primaryColor) || defaultPrimaryColor
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
getCVData,
|
getCVData,
|
||||||
getCVSavedTime,
|
getCVSavedTime,
|
||||||
|
getPrimaryColor,
|
||||||
} from '../lib/store';
|
} from '../lib/store';
|
||||||
import { renderThemeOn } from '../themes';
|
import { renderThemeOn } from '../themes';
|
||||||
import { getCVTitle } from '../themes/data';
|
import { getCVTitle } from '../themes/data';
|
||||||
|
|
@ -33,7 +34,7 @@ const restoreScrollPosition = () => {
|
||||||
// Render CV
|
// Render CV
|
||||||
const data = getCVData()
|
const data = getCVData()
|
||||||
if (data) {
|
if (data) {
|
||||||
renderThemeOn(themeName, elCV, data)
|
renderThemeOn(themeName, elCV, data, getPrimaryColor())
|
||||||
// change document title
|
// change document title
|
||||||
document.title = getCVTitle(data)
|
document.title = getCVTitle(data)
|
||||||
// restore scroll position
|
// restore scroll position
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
@use '../basic';
|
@use '../basic';
|
||||||
|
@use '../vars';
|
||||||
@use 'json-editor';
|
@use 'json-editor';
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|
@ -72,9 +73,42 @@ button {
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-actions {
|
.app-actions {
|
||||||
button {
|
> * {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.color-picker {
|
||||||
|
@include vars.button-base;
|
||||||
|
border: 1px solid var(--grey-2);
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
display: block;
|
||||||
|
width: 85px;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-area {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=color] {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 24px;
|
||||||
|
@supports (-moz-appearance:none) {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.output-html {
|
.output-html {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { reformatDate } from '../lib/date';
|
||||||
import { getIconSVG } from '../lib/icons';
|
import { getIconSVG } from '../lib/icons';
|
||||||
import { renderMarkdown } from '../lib/markdown';
|
import { renderMarkdown } from '../lib/markdown';
|
||||||
|
|
||||||
|
export const primaryColorVarName = '--color-primary'
|
||||||
|
|
||||||
export function getRenderData(cvData) {
|
export function getRenderData(cvData) {
|
||||||
return {
|
return {
|
||||||
cv: cvData,
|
cv: cvData,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import ejs from 'ejs';
|
import ejs from 'ejs';
|
||||||
|
|
||||||
import { getRenderData } from './data';
|
import {
|
||||||
|
getRenderData,
|
||||||
|
primaryColorVarName,
|
||||||
|
} from './data';
|
||||||
|
|
||||||
const themes = {}
|
const themes = {}
|
||||||
|
|
||||||
|
|
@ -36,7 +39,7 @@ export function renderTheme(template, cvData, options) {
|
||||||
|
|
||||||
const cvStyleId = 'cv-style'
|
const cvStyleId = 'cv-style'
|
||||||
|
|
||||||
export function renderThemeOn(name, el, data) {
|
export function renderThemeOn(name, el, data, primaryColor) {
|
||||||
const theme = getTheme(name)
|
const theme = getTheme(name)
|
||||||
el.innerHTML = renderTheme(theme.template, data)
|
el.innerHTML = renderTheme(theme.template, data)
|
||||||
|
|
||||||
|
|
@ -46,4 +49,6 @@ export function renderThemeOn(name, el, data) {
|
||||||
document.head.appendChild(elStyle)
|
document.head.appendChild(elStyle)
|
||||||
}
|
}
|
||||||
elStyle.innerHTML = theme.style
|
elStyle.innerHTML = theme.style
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty(primaryColorVarName, primaryColor)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
/* Naming convention: https://ricostacruz.com/rscss/ */
|
/* Naming convention: https://ricostacruz.com/rscss/ */
|
||||||
|
|
||||||
$color-signature: #2A3FFB;
|
|
||||||
$color-text-dim: #777;
|
$color-text-dim: #777;
|
||||||
$color-text-dimmer: #999;
|
$color-text-dimmer: #999;
|
||||||
$color-border-dim: #aaa;
|
$color-border-dim: #aaa;
|
||||||
|
|
@ -11,6 +10,11 @@ $fz-3: 18px;
|
||||||
$fz-4: 16px;
|
$fz-4: 16px;
|
||||||
$lh-p: 1.4;
|
$lh-p: 1.4;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
// overide `--color-primary` in your own css
|
||||||
|
--color-primary: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
.cv-container {
|
.cv-container {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: system-ui, sans-serif;
|
font-family: system-ui, sans-serif;
|
||||||
|
|
@ -31,11 +35,11 @@ $lh-p: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
a, a:visited, a:active {
|
a, a:visited, a:active {
|
||||||
color: $color-signature;
|
color: var(--color-primary);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
color: $color-signature;
|
color: var(--color-primary);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,14 +51,14 @@ section {
|
||||||
h2 {
|
h2 {
|
||||||
font-size: $fz-2;
|
font-size: $fz-2;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $color-signature;
|
color: var(--color-primary);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.line {
|
.line {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin: 14px 0 0 1em;
|
margin: 14px 0 0 1em;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background-color: $color-signature;
|
background-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ import { ViteEjsPlugin } from 'vite-plugin-ejs';
|
||||||
import { viteSingleFile } from 'vite-plugin-singlefile';
|
import { viteSingleFile } from 'vite-plugin-singlefile';
|
||||||
|
|
||||||
import { TransformEjs } from './src/lib/vite-plugins';
|
import { TransformEjs } from './src/lib/vite-plugins';
|
||||||
import { getRenderData } from './src/themes/data';
|
import {
|
||||||
|
getRenderData,
|
||||||
|
primaryColorVarName,
|
||||||
|
} from './src/themes/data';
|
||||||
|
|
||||||
const dataFilename = process.env.DATA_FILENAME || './sample.cv.json'
|
const dataFilename = process.env.DATA_FILENAME || './sample.cv.json'
|
||||||
const outDir = process.env.OUT_DIR || 'dist'
|
const outDir = process.env.OUT_DIR || 'dist'
|
||||||
|
|
@ -12,6 +15,10 @@ const outDir = process.env.OUT_DIR || 'dist'
|
||||||
const data = require(dataFilename)
|
const data = require(dataFilename)
|
||||||
const renderData = getRenderData(data)
|
const renderData = getRenderData(data)
|
||||||
renderData.theme = process.env.THEME || 'reorx'
|
renderData.theme = process.env.THEME || 'reorx'
|
||||||
|
renderData.primaryColor = {
|
||||||
|
var: primaryColorVarName,
|
||||||
|
value: process.env.PRIMARY_COLOR || '#2A3FFB'
|
||||||
|
}
|
||||||
renderData.isProduction = process.env.NODE_ENV === 'production'
|
renderData.isProduction = process.env.NODE_ENV === 'production'
|
||||||
renderData.meta = {
|
renderData.meta = {
|
||||||
title: data.basics.name,
|
title: data.basics.name,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue