diff --git a/package-lock.json b/package-lock.json index 890cc30d..1022a75c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,13 @@ "version": "2.5.0", "license": "GPL-2.0-or-later", "dependencies": { + "@codemirror/basic-setup": "^0.20.0", "@codemirror/lang-json": "^6.0.1", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.33.0", "@uiw/react-codemirror": "^4.23.1", "@wordpress/icons": "^10.7.0", + "diff": "^7.0.0", "lib-font": "^2.4.3" }, "devDependencies": { @@ -1981,6 +1985,112 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@codemirror/basic-setup": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/basic-setup/-/basic-setup-0.20.0.tgz", + "integrity": "sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==", + "deprecated": "In version 6.0, this package has been renamed to just 'codemirror'", + "dependencies": { + "@codemirror/autocomplete": "^0.20.0", + "@codemirror/commands": "^0.20.0", + "@codemirror/language": "^0.20.0", + "@codemirror/lint": "^0.20.0", + "@codemirror/search": "^0.20.0", + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0" + } + }, + "node_modules/@codemirror/basic-setup/node_modules/@codemirror/autocomplete": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-0.20.3.tgz", + "integrity": "sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==", + "dependencies": { + "@codemirror/language": "^0.20.0", + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0", + "@lezer/common": "^0.16.0" + } + }, + "node_modules/@codemirror/basic-setup/node_modules/@codemirror/commands": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-0.20.0.tgz", + "integrity": "sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==", + "dependencies": { + "@codemirror/language": "^0.20.0", + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0", + "@lezer/common": "^0.16.0" + } + }, + "node_modules/@codemirror/basic-setup/node_modules/@codemirror/language": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz", + "integrity": "sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==", + "dependencies": { + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0", + "@lezer/common": "^0.16.0", + "@lezer/highlight": "^0.16.0", + "@lezer/lr": "^0.16.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/basic-setup/node_modules/@codemirror/lint": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-0.20.3.tgz", + "integrity": "sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==", + "dependencies": { + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.2", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/basic-setup/node_modules/@codemirror/search": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-0.20.1.tgz", + "integrity": "sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==", + "dependencies": { + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/basic-setup/node_modules/@codemirror/state": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz", + "integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==" + }, + "node_modules/@codemirror/basic-setup/node_modules/@codemirror/view": { + "version": "0.20.7", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz", + "integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==", + "dependencies": { + "@codemirror/state": "^0.20.0", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@codemirror/basic-setup/node_modules/@lezer/common": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz", + "integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==" + }, + "node_modules/@codemirror/basic-setup/node_modules/@lezer/highlight": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz", + "integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==", + "dependencies": { + "@lezer/common": "^0.16.0" + } + }, + "node_modules/@codemirror/basic-setup/node_modules/@lezer/lr": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz", + "integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==", + "dependencies": { + "@lezer/common": "^0.16.0" + } + }, "node_modules/@codemirror/commands": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz", @@ -2051,9 +2161,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz", - "integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==", + "version": "6.33.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.33.0.tgz", + "integrity": "sha512-AroaR3BvnjRW8fiZBalAaK+ZzB5usGgI014YKElYZvQdNH5ZIidHlO+cyf/2rWzyBFRkvG6VhiXeAEbC53P2YQ==", "dependencies": { "@codemirror/state": "^6.4.0", "style-mod": "^4.1.0", @@ -8683,6 +8793,14 @@ "integrity": "sha512-oD9vGBV2wTc7fAzAM6KC0chSgs234V8+qDEeK+mcbRj2UvcuA7lgBztGi/opj/iahcXD3BSj8Ymvib628yy9FA==", "dev": true }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", diff --git a/package.json b/package.json index f016b13e..7422df34 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,13 @@ "npm": ">=10.2.3" }, "dependencies": { + "@codemirror/basic-setup": "^0.20.0", "@codemirror/lang-json": "^6.0.1", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.33.0", "@uiw/react-codemirror": "^4.23.1", "@wordpress/icons": "^10.7.0", + "diff": "^7.0.0", "lib-font": "^2.4.3" }, "devDependencies": { diff --git a/src/editor-sidebar/code-mirror-diff-viewer.js b/src/editor-sidebar/code-mirror-diff-viewer.js new file mode 100644 index 00000000..9d765b9a --- /dev/null +++ b/src/editor-sidebar/code-mirror-diff-viewer.js @@ -0,0 +1,133 @@ +/** + * External dependencies + */ +import CodeMirror from '@uiw/react-codemirror'; +import { EditorView, ViewPlugin, Decoration } from '@codemirror/view'; +import { EditorState, RangeSetBuilder } from '@codemirror/state'; +import { basicSetup } from '@codemirror/basic-setup'; +import { json } from '@codemirror/lang-json'; +import { diffLines } from 'diff'; + +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { useState, useEffect } from '@wordpress/element'; + +export function CodeMirrorDiffViewer({ oldCode, newCode }) { + const [diff, setDiff] = useState([]); + + useEffect(() => { + // override for testing + const oldCode = { + "hello": "123", + "world": "456", + "foo": "bar" + }; + const newCode = { + "hello": "123", + "world": "456", + "foo": "baz" + }; + + setDiff(diffLines(JSON.stringify(oldCode, null, 4), JSON.stringify(newCode, null, 4))); + }, [oldCode, newCode]); + + const diffDecorations = EditorView.decorations.compute([], (state) => getDiffDecorations(diff)); + // const diffDecorations = (diff) => { + // const builder = new RangeSetBuilder(); + // diff.forEach((part, index) => { + // if (part.added || part.removed) { + // const className = part.added ? 'added' : 'removed'; + // const decoration = Decoration.mark({ + // class: className, + // }); + // builder.add(index, index + part.value.length, decoration); + // } + // }); + // return builder.finish(); + // }; + + // const diffPlugin = ViewPlugin.fromClass(class { + // constructor(view) { + // this.decorations = diffDecorations(diff); + // } + // update(update) { + // if (update.docChanged || update.viewportChanged) { + // this.decorations = diffDecorations(diff); + // } + // } + // }, { + // decorations: v => v.decorations + // }); + + // const state = EditorState.create({ + // doc: newCode, + // extensions: [basicSetup, json(), diffPlugin] + // }); + + // return ; + + return ( +
+ part.value).join('') } + extensions={ [ json(), diffDecorations ] } + readOnly + /> + +
+ ); +} + +export function getDiffDecorations(diff) { + let decorations = []; + let lineNumber = 0; + + diff.forEach(part => { + const lines = part.value.split('\n'); + lines.forEach((line, index) => { + if (line === '') return; // Skip empty lines + + const from = lineNumber + index; + const to = from; // Single line + + if (part.added) { + decorations.push(Decoration.line({ class: "cbt-code-mirror-line-added" }).range(from, to)); + } else if (part.removed) { + decorations.push(Decoration.line({ class: "cbt-code-mirror-line-removed" }).range(from, to)); + } + }); + + lineNumber += lines.length - 1; + }); + + return Decoration.set(decorations); +} + +export function mergeDeep(target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return mergeDeep(target, ...sources); +} + +function isObject(item) { + return (item && typeof item === 'object' && !Array.isArray(item)); +} diff --git a/src/editor-sidebar/json-editor-modal.js b/src/editor-sidebar/json-editor-modal.js index 11d367ca..f6c4d1b0 100644 --- a/src/editor-sidebar/json-editor-modal.js +++ b/src/editor-sidebar/json-editor-modal.js @@ -1,9 +1,3 @@ -/** - * External dependencies - */ -import CodeMirror from '@uiw/react-codemirror'; -import { json } from '@codemirror/lang-json'; - /** * WordPress dependencies */ @@ -16,6 +10,7 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import { fetchThemeJson } from '../resolvers'; +import { CodeMirrorDiffViewer } from './code-mirror-diff-viewer'; const ThemeJsonEditorModal = ( { onRequestClose } ) => { const [ themeData, setThemeData ] = useState( '' ); @@ -42,12 +37,7 @@ const ThemeJsonEditorModal = ( { onRequestClose } ) => { onRequestClose={ onRequestClose } className="create-block-theme__theme-json-modal" > - + ); };