From 0ec445dc48ea04faae21fb04dd03e15bb67d30d5 Mon Sep 17 00:00:00 2001 From: Anton Timmermans Date: Fri, 3 Aug 2018 14:08:12 +0200 Subject: [PATCH] Add Annotations documentation and debugging --- package-lock.json | 94 +++++++++++++++++++ package.json | 1 + .../components/rich-text/annotations/index.js | 74 +++++++++++++-- 3 files changed, 162 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index f86103565e59a5..d974222c2202c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5346,6 +5346,12 @@ } } }, + "base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=", + "dev": true + }, "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", @@ -8813,6 +8819,15 @@ "bser": "^2.0.0" } }, + "fbemitter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-2.1.1.tgz", + "integrity": "sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=", + "dev": true, + "requires": { + "fbjs": "^0.8.4" + } + }, "fbjs": { "version": "0.8.17", "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", @@ -8993,6 +9008,16 @@ "readable-stream": "^2.0.4" } }, + "flux": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/flux/-/flux-3.1.3.tgz", + "integrity": "sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io=", + "dev": true, + "requires": { + "fbemitter": "^2.0.0", + "fbjs": "^0.8.0" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -13708,6 +13733,12 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=", + "dev": true + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -13720,6 +13751,12 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.flow": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", + "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=", + "dev": true + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -16876,6 +16913,12 @@ } } }, + "pure-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", + "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=", + "dev": true + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -17060,6 +17103,18 @@ "prop-types": "^15.5.6" } }, + "react-base16-styling": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", + "integrity": "sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=", + "dev": true, + "requires": { + "base16": "^1.0.0", + "lodash.curry": "^4.0.1", + "lodash.flow": "^3.3.0", + "pure-color": "^1.2.0" + } + }, "react-click-outside": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/react-click-outside/-/react-click-outside-2.3.1.tgz", @@ -17130,6 +17185,24 @@ "integrity": "sha512-xpb0PpALlFWNw/q13A+1aHeyJyLYCg0/cCHPUA43zYluZuIPHaHL3k8OBsTgQtxqW0FhyDEMvi8fZ/+7+r4OSQ==", "dev": true }, + "react-json-view": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.19.1.tgz", + "integrity": "sha512-u5e0XDLIs9Rj43vWkKvwL8G3JzvXSl6etuS5G42a8klMohZuYFQzSN6ri+/GiBptDqlrXPTdExJVU7x9rrlXhg==", + "dev": true, + "requires": { + "flux": "^3.1.3", + "react-base16-styling": "^0.6.0", + "react-lifecycles-compat": "^3.0.4", + "react-textarea-autosize": "^6.1.0" + } + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "dev": true + }, "react-onclickoutside": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.7.1.tgz", @@ -17203,6 +17276,27 @@ } } }, + "react-textarea-autosize": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz", + "integrity": "sha512-F6bI1dgib6fSvG8so1HuArPUv+iVEfPliuLWusLF+gAKz0FbB4jLrWUrTAeq1afnPT2c9toEZYUdz/y1uKMy4A==", + "dev": true, + "requires": { + "prop-types": "^15.6.0" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "dev": true, + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } + } + }, "reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", diff --git a/package.json b/package.json index 95d4cd92356b39..0ea69a966baa6f 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "postcss-loader": "2.1.3", "puppeteer": "1.6.1", "raw-loader": "0.5.1", + "react-json-view": "1.19.1", "react-test-renderer": "16.4.1", "rimraf": "2.6.2", "rtlcss": "2.4.0", diff --git a/packages/editor/src/components/rich-text/annotations/index.js b/packages/editor/src/components/rich-text/annotations/index.js index c42ae217018d49..f0eac9bc143a96 100644 --- a/packages/editor/src/components/rich-text/annotations/index.js +++ b/packages/editor/src/components/rich-text/annotations/index.js @@ -9,15 +9,46 @@ class Annotations extends Component { }; } - shouldComponentUpdate( nextProps, nextState ) { - return nextProps.annotations.length !== this.state.annotationsLength; + /** + * Prevents re-rendering if the same annotations are passed. This prevents + * performance issues when the change tracking in tinyMCE adjusts the + * annotations and this component receives new annotations. + * + * This has the downside that annotations cannot be moved from the outside. This + * can easily be solved by inserting an annotation with a new ID instead. This + * forces a re-render by this component. + * + * @param {Object} nextProps The new props this component will receive. + * + * @return {boolean} Whether this component should be re-rendered. + */ + shouldComponentUpdate( nextProps ) { + return Annotations.combineAnnotationIds( nextProps.annotations ) !== this.state.annotationIds; + } - // return nextProps.annotations !== this.props.annotations; + /** + * Combines annotation ids into a string seperated by dashes. + * + * @param {Object[]} annotations Annotation objects. + * + * @return {string} Combined annotation IDs. + */ + static combineAnnotationIds( annotations ) { + return annotations.map( ( ids, annotation ) => { + return ids + '-' + annotation.id; + } ).join( '-' ); } - static getDerivedStateFromProps( props, state ) { + /** + * Returns state changes necessary for new props. + * + * @param {Object} props The props this component received. + * + * @return {Object} The state changes that are required by the given props. + */ + static getDerivedStateFromProps( props ) { return { - annotationsLength: props.annotations.length, + annotationIds: Annotations.combineAnnotationIds( props.annotations ), }; } @@ -37,7 +68,7 @@ class Annotations extends Component { * * This causes us to have a declarative API on top of tinyMCE's imperative API. * - * @return {null} Always returns null to not render anything. + * @return {WPElement} Might render debugging information. */ render() { const { annotations, editor } = this.props; @@ -62,12 +93,41 @@ class Annotations extends Component { // Catch here because componentDidCatch catches only in child components. } catch ( error ) { + // eslint-disable-next-line no-console console.error( 'Error occurred in anotations module', error ); - this.props.onError(); + // this.props.onError(); + } + + // Debugging information makes it much easier to develop annotations. + // This if should be stripped in the production build. + if ( process.env.NODE_ENV === 'development' ) { + return this.renderDebug(); } return null; } + + /** + * Renders debugging information that makes it much easier to see why a certain + * annotation didn't apply. + * + * @return {WPElement} The rendered React tree. + */ + renderDebug() { + const ReactJson = require( 'react-json-view' ).default; + + const styles = { + border: '1px solid red', + }; + + return
+

Raw HTML content

+ { this.props.editor.getContent() } + +

Annotations JSON

+ +
; + } } export default Annotations;