diff --git a/.eslintrc.json b/.eslintrc.json
index 07a4940..b2adb94 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -4,7 +4,7 @@
"node": true
},
"extends": "eslint:recommended",
- "ignorePatterns": ["test", "vite.config.js"],
+ "ignorePatterns": ["editor.js", "test", "vite.config.js"],
"parserOptions": {
"sourceType": "module"
}
diff --git a/.prettierrc.json b/.prettierrc.json
index f3c3c08..004eaa8 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -1,5 +1,6 @@
{
"arrowParens": "avoid",
+ "printWidth": 120,
"semi": false,
"singleQuote": true
}
diff --git a/editor.css b/editor.css
new file mode 100644
index 0000000..498ee4c
--- /dev/null
+++ b/editor.css
@@ -0,0 +1,55 @@
+html,
+body {
+ height: 100%;
+}
+
+html {
+ color-scheme: light dark;
+}
+
+body {
+ display: grid;
+ grid-template: 50% 50% / 100%;
+ margin: 0;
+}
+
+button {
+ cursor: pointer;
+}
+
+iframe {
+ border: 0;
+ order: 1;
+ width: 100%;
+ height: 100%;
+}
+
+nav {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ position: absolute;
+ inset-block-end: 1rem;
+ inset-inline-end: 1rem;
+}
+
+nav > * {
+ background: ButtonFace;
+ border: 1px solid ButtonBorder;
+ border-radius: 50%;
+ color: ButtonText;
+ padding: 0.5rem;
+ display: grid;
+ place-items: center;
+}
+
+nav > :hover,
+nav > :focus {
+ background: Canvas;
+}
+
+@media (min-width: 720px) {
+ body {
+ grid-template: 100% / 50% 50%;
+ }
+}
diff --git a/editor.js b/editor.js
new file mode 100644
index 0000000..787b892
--- /dev/null
+++ b/editor.js
@@ -0,0 +1,33 @@
+import { indentWithTab } from '@codemirror/commands'
+import { json } from '@codemirror/lang-json'
+import { EditorView, keymap } from '@codemirror/view'
+import { oneDark } from '@codemirror/theme-one-dark'
+import { basicSetup } from 'codemirror'
+import { debounce } from 'debounce'
+import resume from 'resume-schema/sample.resume.json' assert { type: 'json' }
+
+import { render } from './index.js'
+import './editor.css'
+
+const preview = document.querySelector('iframe')
+const printButton = document.querySelector('button[name=print]')
+
+printButton.addEventListener('click', () => preview.contentWindow.print())
+
+const renderPreview = debounce(resume => (preview.srcdoc = render(resume)), 200)
+renderPreview(resume)
+
+new EditorView({
+ doc: JSON.stringify(resume, null, ' '),
+ extensions: [
+ basicSetup,
+ oneDark,
+ EditorView.lineWrapping,
+ EditorView.updateListener.of(
+ ({ docChanged, state }) => docChanged && renderPreview(JSON.parse(state.doc.toString())),
+ ),
+ keymap.of([indentWithTab]),
+ json(),
+ ],
+ parent: document.body,
+})
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..a70f9d8
--- /dev/null
+++ b/index.html
@@ -0,0 +1,34 @@
+
+
+
+
+ jsonresume-theme-even
+
+
+
+
+
+
+
+
+
diff --git a/netlify.toml b/netlify.toml
index 9c49a2a..1baf1d5 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -1,3 +1,3 @@
[build]
- command = "npm run build:demo"
+ command = "npm run build -- --mode development"
publish = "public"
diff --git a/package-lock.json b/package-lock.json
index 5d68181..42b63f0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,7 +17,13 @@
"jsonresume-theme-even": "bin/cli.js"
},
"devDependencies": {
+ "@codemirror/commands": "6.2.5",
+ "@codemirror/lang-json": "6.0.1",
+ "@codemirror/theme-one-dark": "6.1.2",
+ "@codemirror/view": "6.19.0",
"@vitest/coverage-v8": "0.34.3",
+ "codemirror": "6.0.1",
+ "debounce": "1.2.1",
"eslint": "8.48.0",
"html-validate": "8.3.0",
"husky": "8.0.3",
@@ -235,6 +241,111 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
+ "node_modules/@codemirror/autocomplete": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.9.0.tgz",
+ "integrity": "sha512-Fbwm0V/Wn3BkEJZRhr0hi5BhCo5a7eBL6LYaliPjOSwCyfOpnjXY59HruSxOUNV+1OYer0Tgx1zRNQttjXyDog==",
+ "dev": true,
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.6.0",
+ "@lezer/common": "^1.0.0"
+ },
+ "peerDependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/commands": {
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.5.tgz",
+ "integrity": "sha512-dSi7ow2P2YgPBZflR9AJoaTHvqmeGIgkhignYMd5zK5y6DANTvxKxp6eMEpIDUJkRAaOY/TFZ4jP1ADIO/GLVA==",
+ "dev": true,
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.2.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-json": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
+ "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
+ "dev": true,
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@lezer/json": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/language": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.9.0.tgz",
+ "integrity": "sha512-nFu311/0ne/qGuGCL3oKuktBgzVOaxCHZPZv1tLSZkNjPYxxvkjSbzno3MlErG2tgw1Yw1yF8BxMCegeMXqpiw==",
+ "dev": true,
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.0.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0",
+ "style-mod": "^4.0.0"
+ }
+ },
+ "node_modules/@codemirror/lint": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.4.1.tgz",
+ "integrity": "sha512-2Hx945qKX7FBan5/gUdTM8fsMYrNG9clIgEcPXestbLVFAUyQYFAuju/5BMNf/PwgpVaX5pvRm4+ovjbp9D9gQ==",
+ "dev": true,
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/search": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.2.tgz",
+ "integrity": "sha512-WRihpqd0l9cEh9J3IZe45Yi+Z5MfTsEXnyc3V7qXHP4ZYtIYpGOn+EJ7fyLIkyAm/8S6QIr7/mMISfAadf8zCg==",
+ "dev": true,
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/state": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz",
+ "integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==",
+ "dev": true
+ },
+ "node_modules/@codemirror/theme-one-dark": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
+ "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
+ "dev": true,
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/highlight": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/view": {
+ "version": "6.19.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.19.0.tgz",
+ "integrity": "sha512-XqNIfW/3GaaF+T7Q1jBcRLCPm1NbrR2DBxrXacSt1FG+rNsdsNn3/azAfgpUoJ7yy4xgd8xTPa3AlL+y0lMizQ==",
+ "dev": true,
+ "dependencies": {
+ "@codemirror/state": "^6.1.4",
+ "style-mod": "^4.1.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
@@ -801,6 +912,40 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@lezer/common": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.4.tgz",
+ "integrity": "sha512-lZHlk8p67x4aIDtJl6UQrXSOP6oi7dQR3W/geFVrENdA1JDaAJWldnVqVjPMJupbTKbzDfFcePfKttqVidS/dg==",
+ "dev": true
+ },
+ "node_modules/@lezer/highlight": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz",
+ "integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==",
+ "dev": true,
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/json": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.1.tgz",
+ "integrity": "sha512-nkVC27qiEZEjySbi6gQRuMwa2sDu2PtfjSgz0A4QF81QyRGm3kb2YRzLcOPcTEtmcwvrX/cej7mlhbwViA4WJw==",
+ "dev": true,
+ "dependencies": {
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/lr": {
+ "version": "1.3.10",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.10.tgz",
+ "integrity": "sha512-BZfVvf7Re5BIwJHlZXbJn9L8lus5EonxQghyn+ih8Wl36XMFBPTXC0KM0IdUtj9w/diPHsKlXVgL+AlX2jYJ0Q==",
+ "dev": true,
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1335,6 +1480,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/codemirror": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+ "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+ "dev": true,
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/commands": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/search": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1390,6 +1550,12 @@
"url": "https://opencollective.com/core-js"
}
},
+ "node_modules/crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
+ "dev": true
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1404,6 +1570,12 @@
"node": ">= 8"
}
},
+ "node_modules/debounce": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
+ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
+ "dev": true
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -3894,6 +4066,12 @@
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz",
"integrity": "sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw=="
},
+ "node_modules/style-mod": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
+ "integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==",
+ "dev": true
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -4251,6 +4429,12 @@
}
}
},
+ "node_modules/w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
+ "dev": true
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 0768169..595c5a6 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "jsonresume-theme-even",
"version": "0.21.0",
- "description": "A flat theme for JSON Resume, compatible with the bleeding edge resume schema",
+ "description": "A flat JSON Resume theme, compatible with the latest resume schema",
"keywords": [
"resume",
"json",
@@ -38,8 +38,7 @@
],
"scripts": {
"build": "vite build",
- "prebuild:demo": "npm run build",
- "build:demo": "mkdir -p public && ./bin/cli.js < node_modules/resume-schema/sample.resume.json > public/index.html",
+ "dev": "vite",
"format": "prettier .",
"lint": "eslint --ignore-path .gitignore .",
"prepare": "husky install",
@@ -52,7 +51,13 @@
"striptags": "^3.2.0"
},
"devDependencies": {
+ "@codemirror/commands": "6.2.5",
+ "@codemirror/lang-json": "6.0.1",
+ "@codemirror/theme-one-dark": "6.1.2",
+ "@codemirror/view": "6.19.0",
"@vitest/coverage-v8": "0.34.3",
+ "codemirror": "6.0.1",
+ "debounce": "1.2.1",
"eslint": "8.48.0",
"html-validate": "8.3.0",
"husky": "8.0.3",
diff --git a/vite.config.js b/vite.config.js
index 30ef75f..9ed2047 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,21 +1,33 @@
import { defineConfig } from 'vite'
import pkg from './package.json' assert { type: 'json' }
-export default defineConfig({
- build: {
- copyPublicDir: false,
- lib: {
- entry: './index.js',
- fileName: 'index',
- formats: ['es', 'cjs', 'umd'],
- name: 'jsonresumeThemeEven',
+export default defineConfig(({ mode }) => {
+ if (mode === 'development') {
+ return {
+ build: {
+ outDir: './public',
+ target: 'esnext',
+ },
+ publicDir: false,
+ }
+ }
+
+ return {
+ build: {
+ copyPublicDir: false,
+ lib: {
+ entry: './index.js',
+ fileName: 'index',
+ formats: ['es', 'cjs', 'umd'],
+ name: 'jsonresumeThemeEven',
+ },
+ rollupOptions: {
+ external: [...Object.keys(pkg.dependencies), /^node:.*/],
+ },
+ target: 'esnext',
+ test: {
+ clearMocks: true,
+ },
},
- rollupOptions: {
- external: [...Object.keys(pkg.dependencies), /^node:.*/],
- },
- target: 'esnext',
- test: {
- clearMocks: true,
- },
- },
+ }
})