diff --git a/editor/_json-editor.scss b/editor/_json-editor.scss index 7a62f51..2045461 100644 --- a/editor/_json-editor.scss +++ b/editor/_json-editor.scss @@ -1,11 +1,64 @@ +@mixin button-base { + appearance: none; + user-select: none; + vertical-align: middle; + outline: 0; + display: inline-block; + cursor: pointer; + position: relative; +} + +@mixin normal-button { + @include button-base; + + background-color: #eee; + border: 1px solid transparent; + border-radius: 2px; + padding: 0px 8px; + line-height: 22px; + + &:hover { + background-color: #e1e1e1; + } + &:active { + border-color: #555; + } +} + +@mixin text-button { + @include button-base; + background-color: transparent; + padding: 0; + font-size: 12px; + line-height: 1; + color: #555; + &:hover { + border-bottom: 1px solid #999; + } + &:active { + border-color: transparent; + border-bottom: 1px solid #000; + } +} + .editor-container { font-size: 14px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; --gap: 8px; + --blue-0: #D0EBFF; + --blue-1: #A5D8FF; + --red-0: #FFE3E3; + --red-1: #FFC9C9; + --yellow-0: #FFF3BF; + --yellow-1: #FFEC99; + --green-0: #D3F9D8; + --green-1: #B2F2BB; h1 {} h2 {} - h3 {} + h3 { + margin: 12px 0 12px 0; + } p { margin: 0 0 var(--gap) 0; } @@ -18,12 +71,46 @@ line-height: 1.3; padding: var(--gap); } + input[type=text], + input[type=email], + textarea { + border: 1px solid #888; + border-radius: 2px; + } + button { + @include normal-button; + + margin-right: var(--gap); + // last-of-type should not be used here + &:last-child { + margin-right: 0; + } + + svg { + vertical-align: middle; + font-size: 14px; + position: relative; + bottom: 1px; + } + } + iconify-icon { + font-size: 15px; + } + + .je-header { + label { + font-weight: 700; + vertical-align: middle; + } + } // override theme-default .je-indented-panel { - padding-left: var(--gap); - margin-left: var(--gap); - border-left: 1px solid #ccc + padding-left: calc(var(--gap) * 2); + padding-bottom: var(--gap); + margin-left: 0; + border-left: 1px solid #ddd; + margin-bottom: var(--gap); } .je-indented-panel--top { @@ -31,10 +118,6 @@ margin-left: var(--gap); } - .je-object__container { - - } - .form-control { margin-bottom: var(--gap); } @@ -42,7 +125,7 @@ .je-form-input-label { display: block; margin-bottom: calc(var(--gap) / 2); - font-weight: bold; + font-weight: 500; } .je-form-input-description { margin: 0; @@ -63,6 +146,36 @@ border-bottom: 1px solid #ccc; } + /* buttons */ + .je-object__controls { + display: inline-block; + position: relative; + margin-left: calc(var(--gap) * 2); + top: 2px; + + button { + @include text-button; + } + } + .json-editor-btn-add { + background-color: var(--green-0); + &:hover { + background-color: var(--green-1); + } + } + .json-editor-btn-subtract { + background-color: var(--yellow-0); + &:hover { + background-color: var(--yellow-1); + } + } + .json-editor-btn-delete { + background-color: var(--red-0); + &:hover { + background-color: var(--red-1); + } + } + .je-textarea { height: 150px; min-height: 150px; diff --git a/editor/iconlib.js b/editor/iconlib.js new file mode 100644 index 0000000..5ab713d --- /dev/null +++ b/editor/iconlib.js @@ -0,0 +1,40 @@ +import { AbstractIconLib } from '@json-editor/json-editor/src/iconlib.js'; + +import { getIconSVG } from '../icons'; + +const iconMapping = { + collapse: 'mdi:chevron-down', + expand: 'mdi:chevron-right', + delete: 'mdi:delete', + edit: 'mdi:pen', + add: 'mdi:plus', + subtract: 'mdi:minus', + cancel: 'mdi:cancel', + save: 'mdi:content-save', + moveup: 'mdi:arrow-up', + moveright: 'mdi:arrow-right', + movedown: 'mdi:arrow-down', + moveleft: 'mdi:arrow-left', + copy: 'mdi:content-copy', + clear: 'mdi:close-circle', + time: 'mdi:clock', + calendar: 'mdi:calendar', + edit_properties: 'mdi:format-list-bulleted', +} + + +export class MyIconLib extends AbstractIconLib { + + getIcon(key) { + const svg = getIconSVG(iconMapping[key], {dom: true}) + return svg + // const i = document.createElement('iconify-icon') + // i.setAttribute('icon', ) + // return i + } + +} + +export function registerIconLib(JSONEditor) { + JSONEditor.defaults.iconlibs['myiconlib'] = MyIconLib +} diff --git a/editor/main.js b/editor/main.js index 6b43d1b..3b3b26c 100644 --- a/editor/main.js +++ b/editor/main.js @@ -1,11 +1,17 @@ +import 'iconify-icon'; // import only + import objectPath from 'object-path'; import { JSONEditor } from '@json-editor/json-editor/dist/jsoneditor'; import * as exampleData from '../sample.resume.json'; -import * as jsoncvSchema from '../schema/jsoncv.schema.json'; +import * as jsoncvSchemaModule from '../schema/jsoncv.schema.json'; +import { registerIconLib } from './iconlib'; import { registerTheme } from './theme'; -import { createElement } from './utils'; +import { + createElement, + traverseDownObject, +} from './utils'; const propertiesInOrder = ['basics', 'education', 'work', 'skills', 'projects', 'languages', 'interests', 'references', 'awards', 'publications', 'volunteer'] const basicsPropertiesInOrder = ['name', 'label', 'email', 'phone', 'url', 'summary', 'image', 'location', 'profiles'] @@ -19,7 +25,9 @@ const basicsUl = createElement('ul', { parent: tocUl }) -const attrSchemaPathTo = 'data-schemapath-to' + +// 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) => { @@ -50,6 +58,15 @@ basicsPropertiesInOrder.forEach((name, index) => { }) }) +// 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', @@ -58,12 +75,17 @@ for (const [key, format] of Object.entries(keyFormatMap)) { objectPath.get(jsoncvSchema.properties, key).format = format } +// change schema title +jsoncvSchema.title = 'Resume' + // initialize editor registerTheme(JSONEditor) +registerIconLib(JSONEditor) const elEditorContainer = document.querySelector('.editor-container') const editor = new JSONEditor(elEditorContainer, { schema: jsoncvSchema, theme: 'mytheme', + iconlib: 'myiconlib', }); editor.on('ready',() => { editor.setValue(exampleData) @@ -72,6 +94,5 @@ editor.on('ready',() => { document.querySelectorAll('[data-schemapath]').forEach(el => { const schemapath = el.getAttribute('data-schemapath') el.id = schemapath - console.log('el', schemapath) }) }) diff --git a/editor/utils.js b/editor/utils.js index 092202f..455d412 100644 --- a/editor/utils.js +++ b/editor/utils.js @@ -14,3 +14,13 @@ export const createElement = function(tagName, {className, text, attrs, parent}) } return el } + +export const traverseDownObject = function(obj, callback) { + for (const key in obj) { + const value = obj[key] + if (typeof value === 'object') { + callback(key, value) + traverseDownObject(value, callback) + } + } +} diff --git a/package-lock.json b/package-lock.json index a280c36..e91fb86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,11 @@ "name": "jsoncv", "version": "1.0.0", "dependencies": { + "@iconify/json": "^2.2.15", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", "dayjs": "^1.11.7", - "iconify-icon": "^1.0.0", + "iconify-icon": "^1.0.3", "object-path": "^0.11.8" }, "devDependencies": { @@ -86,6 +87,15 @@ "@iconify/types": "*" } }, + "node_modules/@iconify/json": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.15.tgz", + "integrity": "sha512-+BVLIjTJpBiEOGD3xhCY7/ajH+7QTl/jzF59gf9Hf5y/HyU8D+HUmOsXEGLIsCZErEQB66wZ36AziOlYf3wuPA==", + "dependencies": { + "@iconify/types": "*", + "pathe": "^1.0.0" + } + }, "node_modules/@iconify/types": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", @@ -847,14 +857,14 @@ } }, "node_modules/iconify-icon": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/iconify-icon/-/iconify-icon-1.0.0.tgz", - "integrity": "sha512-UY4PDCKQPpIGgDIx2yxM8wiOdMdLz0mb93dTV0Ox5hThwO8OA2zy8gDmjRReKqkPHK/mY7p/ivDaDGHE8O9xIw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/iconify-icon/-/iconify-icon-1.0.3.tgz", + "integrity": "sha512-pyWLbx8IBfD2G3M0hULuvUBwoowrZtXEKyeAXhD2AlbNYTSDPmWGhgPYaRAnVIuBjMAZ4dCyEHmaYgnlDZc0XQ==", "dependencies": { "@iconify/types": "^2.0.0" }, "funding": { - "url": "http://github.com/sponsors/cyberalien" + "url": "https://github.com/sponsors/cyberalien" } }, "node_modules/immutable": { @@ -1196,6 +1206,11 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/pathe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", + "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -1975,6 +1990,15 @@ "@iconify/types": "*" } }, + "@iconify/json": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.15.tgz", + "integrity": "sha512-+BVLIjTJpBiEOGD3xhCY7/ajH+7QTl/jzF59gf9Hf5y/HyU8D+HUmOsXEGLIsCZErEQB66wZ36AziOlYf3wuPA==", + "requires": { + "@iconify/types": "*", + "pathe": "^1.0.0" + } + }, "@iconify/types": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", @@ -2436,9 +2460,9 @@ "dev": true }, "iconify-icon": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/iconify-icon/-/iconify-icon-1.0.0.tgz", - "integrity": "sha512-UY4PDCKQPpIGgDIx2yxM8wiOdMdLz0mb93dTV0Ox5hThwO8OA2zy8gDmjRReKqkPHK/mY7p/ivDaDGHE8O9xIw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/iconify-icon/-/iconify-icon-1.0.3.tgz", + "integrity": "sha512-pyWLbx8IBfD2G3M0hULuvUBwoowrZtXEKyeAXhD2AlbNYTSDPmWGhgPYaRAnVIuBjMAZ4dCyEHmaYgnlDZc0XQ==", "requires": { "@iconify/types": "^2.0.0" } @@ -2692,6 +2716,11 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "pathe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", + "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", diff --git a/package.json b/package.json index 3172a37..8eca738 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,11 @@ "vite-plugin-handlebars": "^1.6.0" }, "dependencies": { + "@iconify/json": "^2.2.15", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", "dayjs": "^1.11.7", - "iconify-icon": "^1.0.0", + "iconify-icon": "^1.0.3", "object-path": "^0.11.8" } }