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, - }, - }, + } })