├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── gutenberg_js_logo.svg ├── languages └── README.md ├── package.json ├── src ├── js │ ├── gutenberg-overrides │ │ ├── .eslintrc │ │ └── @wordpress │ │ │ ├── edit-post │ │ │ ├── CHANGELOG.md │ │ │ └── build-module │ │ │ │ ├── hooks │ │ │ │ └── components │ │ │ │ │ └── media-upload │ │ │ │ │ └── index.js │ │ │ │ ├── plugins │ │ │ │ └── index.js │ │ │ │ └── store │ │ │ │ └── effects.js │ │ │ ├── editor │ │ │ ├── CHANGELOG.md │ │ │ └── build-module │ │ │ │ ├── components │ │ │ │ ├── autosave-monitor │ │ │ │ │ └── index.js │ │ │ │ ├── inserter │ │ │ │ │ └── menu.js │ │ │ │ ├── post-preview-button │ │ │ │ │ └── index.js │ │ │ │ ├── post-publish-button │ │ │ │ │ └── index.js │ │ │ │ ├── post-publish-panel │ │ │ │ │ └── index.js │ │ │ │ └── post-saved-state │ │ │ │ │ └── index.js │ │ │ │ └── store │ │ │ │ ├── defaults.js │ │ │ │ └── selectors.js │ │ │ └── notices │ │ │ └── build │ │ │ └── store │ │ │ └── actions.js │ ├── index.js │ ├── init │ │ ├── api-fetch.js │ │ ├── index.js │ │ ├── url.js │ │ └── wp.js │ └── scripts │ │ ├── api-fetch.js │ │ ├── data.js │ │ └── editor.js └── scss │ ├── _media-library.scss │ ├── block-library.scss │ └── style.scss └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:react/recommended", 6 | "plugin:jsx-a11y/recommended" 7 | ], 8 | "env": { 9 | "browser": false, 10 | "es6": true, 11 | "node": true 12 | }, 13 | "parserOptions": { 14 | "sourceType": "module", 15 | "ecmaFeatures": { 16 | "jsx": true 17 | } 18 | }, 19 | "globals": { 20 | "window": true, 21 | "document": true, 22 | "localStorage": true, 23 | "sessionStorage": true, 24 | "XMLHttpRequest": true, 25 | "React": true, 26 | "moment": true, 27 | "jQuery": true 28 | }, 29 | "plugins": [ 30 | "react", 31 | "jsx-a11y" 32 | ], 33 | "settings": { 34 | "react": { 35 | "pragma": "React", 36 | "version": "16.0" 37 | } 38 | }, 39 | "rules": { 40 | "arrow-parens": ["warn", "as-needed"], 41 | "arrow-spacing": "warn", 42 | "block-scoped-var": "warn", 43 | "brace-style": [ "warn", "stroustrup", { 44 | "allowSingleLine": true 45 | }], 46 | 47 | "comma-dangle": [ "warn", "always-multiline" ], 48 | "comma-spacing": "warn", 49 | "comma-style": "warn", 50 | 51 | "curly": "warn", 52 | "dot-notation": "warn", 53 | "eol-last": "error", 54 | "eqeqeq": "warn", 55 | 56 | "func-call-spacing": [ "warn", "never" ], 57 | "func-style": [ "error", "declaration", { 58 | "allowArrowFunctions": true 59 | }], 60 | "generator-star-spacing": "warn", 61 | "indent": [ "warn", 2, { 62 | "MemberExpression": 0, 63 | "SwitchCase": 1 64 | }], 65 | "jsx-quotes": "error", 66 | "new-parens": "error", 67 | 68 | "no-caller": "error", 69 | "no-console": "off", 70 | "no-duplicate-imports": "error", 71 | "no-else-return": "warn", 72 | "no-eval": "error", 73 | "no-extra-label": "warn", 74 | "no-floating-decimal": "warn", 75 | "no-lonely-if": "warn", 76 | "no-mixed-operators": "warn", 77 | "no-multi-str": "error", 78 | "no-negated-in-lhs": "warn", 79 | "no-shadow": "warn", 80 | "no-undef-init": "error", 81 | "no-unused-expressions": "error", 82 | "no-useless-computed-key": "warn", 83 | "no-useless-constructor": "warn", 84 | "no-useless-rename": "warn", 85 | "no-useless-return": "warn", 86 | "no-var": "error", 87 | "no-whitespace-before-property": "warn", 88 | 89 | "object-curly-spacing": [ "error", "always" ], 90 | "prefer-const": "warn", 91 | "prefer-rest-params": "warn", 92 | "quote-props": [ "error", "as-needed" ], 93 | "quotes": [ "warn", "single", { 94 | "allowTemplateLiterals": true 95 | }], 96 | 97 | "rest-spread-spacing": ["warn", "never"], 98 | "semi-spacing": "warn", 99 | "semi-style": [ "warn", "last" ], 100 | "semi": [ "error", "always" ], 101 | 102 | "space-before-blocks": "warn", 103 | "space-before-function-paren": "warn", 104 | "space-infix-ops": "warn", 105 | "space-in-parens": ["warn", "never"], 106 | "spaced-comment": ["warn", "always"], 107 | "template-curly-spacing": "warn", 108 | "yield-star-spacing": "warn", 109 | 110 | "valid-jsdoc": ["warn", { 111 | "requireReturn": false, 112 | "requireReturnDescription": false, 113 | "requireParamDescription": false 114 | }], 115 | 116 | "react/display-name": "off", 117 | "react/jsx-curly-spacing": "off", 118 | "react/jsx-equals-spacing": "error", 119 | "react/jsx-indent": [ "warn", 2 ], 120 | "react/jsx-indent-props": [ "warn", 2 ], 121 | "react/jsx-key": "error", 122 | "react/jsx-uses-react": "error", 123 | "react/jsx-uses-vars": "error", 124 | "react/jsx-tag-spacing": "warn", 125 | "react/no-children-prop": "off", 126 | "react/no-find-dom-node": "warn", 127 | "react/no-unescaped-entities": "off", 128 | "react/prop-types": "off", 129 | 130 | "jsx-a11y/label-has-for": [ "error", { "required": "id" }], 131 | "jsx-a11y/media-has-caption": "off", 132 | "jsx-a11y/no-noninteractive-tabindex": "off", 133 | "jsx-a11y/role-has-required-aria-props": "off" 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | package-lock.json 23 | yarn.lock 24 | 25 | languages/gutenberg.pot 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | languages 2 | webpack.config.js 3 | .babelrc 4 | .eslintignore 5 | .eslintrc 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 4 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | Preamble 8 | The licenses for most software are designed to take away your 9 | freedom to share and change it. By contrast, the GNU General Public 10 | License is intended to guarantee your freedom to share and change free 11 | software--to make sure the software is free for all its users. This 12 | General Public License applies to most of the Free Software 13 | Foundation's software and to any other program whose authors commit to 14 | using it. (Some other Free Software Foundation software is covered by 15 | the GNU Lesser General Public License instead.) You can apply it to 16 | your programs, too. 17 | When we speak of free software, we are referring to freedom, not 18 | price. Our General Public Licenses are designed to make sure that you 19 | have the freedom to distribute copies of free software (and charge for 20 | this service if you wish), that you receive source code or can get it 21 | if you want it, that you can change the software or use pieces of it 22 | in new free programs; and that you know you can do these things. 23 | To protect your rights, we need to make restrictions that forbid 24 | anyone to deny you these rights or to ask you to surrender the rights. 25 | These restrictions translate to certain responsibilities for you if you 26 | distribute copies of the software, or if you modify it. 27 | For example, if you distribute copies of such a program, whether 28 | gratis or for a fee, you must give the recipients all the rights that 29 | you have. You must make sure that they, too, receive or can get the 30 | source code. And you must show them these terms so they know their 31 | rights. 32 | We protect your rights with two steps: (1) copyright the software, and 33 | (2) offer you this license which gives you legal permission to copy, 34 | distribute and/or modify the software. 35 | Also, for each author's protection and ours, we want to make certain 36 | that everyone understands that there is no warranty for this free 37 | software. If the software is modified by someone else and passed on, we 38 | want its recipients to know that what they have is not the original, so 39 | that any problems introduced by others will not reflect on the original 40 | authors' reputations. 41 | Finally, any free program is threatened constantly by software 42 | patents. We wish to avoid the danger that redistributors of a free 43 | program will individually obtain patent licenses, in effect making the 44 | program proprietary. To prevent this, we have made it clear that any 45 | patent must be licensed for everyone's free use or not licensed at all. 46 | The precise terms and conditions for copying, distribution and 47 | modification follow. 48 | GNU GENERAL PUBLIC LICENSE 49 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 50 | 0. This License applies to any program or other work which contains 51 | a notice placed by the copyright holder saying it may be distributed 52 | under the terms of this General Public License. The "Program", below, 53 | refers to any such program or work, and a "work based on the Program" 54 | means either the Program or any derivative work under copyright law: 55 | that is to say, a work containing the Program or a portion of it, 56 | either verbatim or with modifications and/or translated into another 57 | language. (Hereinafter, translation is included without limitation in 58 | the term "modification".) Each licensee is addressed as "you". 59 | Activities other than copying, distribution and modification are not 60 | covered by this License; they are outside its scope. The act of 61 | running the Program is not restricted, and the output from the Program 62 | is covered only if its contents constitute a work based on the 63 | Program (independent of having been made by running the Program). 64 | Whether that is true depends on what the Program does. 65 | 1. You may copy and distribute verbatim copies of the Program's 66 | source code as you receive it, in any medium, provided that you 67 | conspicuously and appropriately publish on each copy an appropriate 68 | copyright notice and disclaimer of warranty; keep intact all the 69 | notices that refer to this License and to the absence of any warranty; 70 | and give any other recipients of the Program a copy of this License 71 | along with the Program. 72 | You may charge a fee for the physical act of transferring a copy, and 73 | you may at your option offer warranty protection in exchange for a fee. 74 | 2. You may modify your copy or copies of the Program or any portion 75 | of it, thus forming a work based on the Program, and copy and 76 | distribute such modifications or work under the terms of Section 1 77 | above, provided that you also meet all of these conditions: 78 | a) You must cause the modified files to carry prominent notices 79 | stating that you changed the files and the date of any change. 80 | b) You must cause any work that you distribute or publish, that in 81 | whole or in part contains or is derived from the Program or any 82 | part thereof, to be licensed as a whole at no charge to all third 83 | parties under the terms of this License. 84 | c) If the modified program normally reads commands interactively 85 | when run, you must cause it, when started running for such 86 | interactive use in the most ordinary way, to print or display an 87 | announcement including an appropriate copyright notice and a 88 | notice that there is no warranty (or else, saying that you provide 89 | a warranty) and that users may redistribute the program under 90 | these conditions, and telling the user how to view a copy of this 91 | License. (Exception: if the Program itself is interactive but 92 | does not normally print such an announcement, your work based on 93 | the Program is not required to print an announcement.) 94 | These requirements apply to the modified work as a whole. If 95 | identifiable sections of that work are not derived from the Program, 96 | and can be reasonably considered independent and separate works in 97 | themselves, then this License, and its terms, do not apply to those 98 | sections when you distribute them as separate works. But when you 99 | distribute the same sections as part of a whole which is a work based 100 | on the Program, the distribution of the whole must be on the terms of 101 | this License, whose permissions for other licensees extend to the 102 | entire whole, and thus to each and every part regardless of who wrote it. 103 | Thus, it is not the intent of this section to claim rights or contest 104 | your rights to work written entirely by you; rather, the intent is to 105 | exercise the right to control the distribution of derivative or 106 | collective works based on the Program. 107 | In addition, mere aggregation of another work not based on the Program 108 | with the Program (or with a work based on the Program) on a volume of 109 | a storage or distribution medium does not bring the other work under 110 | the scope of this License. 111 | 3. You may copy and distribute the Program (or a work based on it, 112 | under Section 2) in object code or executable form under the terms of 113 | Sections 1 and 2 above provided that you also do one of the following: 114 | a) Accompany it with the complete corresponding machine-readable 115 | source code, which must be distributed under the terms of Sections 116 | 1 and 2 above on a medium customarily used for software interchange; or, 117 | b) Accompany it with a written offer, valid for at least three 118 | years, to give any third party, for a charge no more than your 119 | cost of physically performing source distribution, a complete 120 | machine-readable copy of the corresponding source code, to be 121 | distributed under the terms of Sections 1 and 2 above on a medium 122 | customarily used for software interchange; or, 123 | c) Accompany it with the information you received as to the offer 124 | to distribute corresponding source code. (This alternative is 125 | allowed only for noncommercial distribution and only if you 126 | received the program in object code or executable form with such 127 | an offer, in accord with Subsection b above.) 128 | The source code for a work means the preferred form of the work for 129 | making modifications to it. For an executable work, complete source 130 | code means all the source code for all modules it contains, plus any 131 | associated interface definition files, plus the scripts used to 132 | control compilation and installation of the executable. However, as a 133 | special exception, the source code distributed need not include 134 | anything that is normally distributed (in either source or binary 135 | form) with the major components (compiler, kernel, and so on) of the 136 | operating system on which the executable runs, unless that component 137 | itself accompanies the executable. 138 | If distribution of executable or object code is made by offering 139 | access to copy from a designated place, then offering equivalent 140 | access to copy the source code from the same place counts as 141 | distribution of the source code, even though third parties are not 142 | compelled to copy the source along with the object code. 143 | 4. You may not copy, modify, sublicense, or distribute the Program 144 | except as expressly provided under this License. Any attempt 145 | otherwise to copy, modify, sublicense or distribute the Program is 146 | void, and will automatically terminate your rights under this License. 147 | However, parties who have received copies, or rights, from you under 148 | this License will not have their licenses terminated so long as such 149 | parties remain in full compliance. 150 | 5. You are not required to accept this License, since you have not 151 | signed it. However, nothing else grants you permission to modify or 152 | distribute the Program or its derivative works. These actions are 153 | prohibited by law if you do not accept this License. Therefore, by 154 | modifying or distributing the Program (or any work based on the 155 | Program), you indicate your acceptance of this License to do so, and 156 | all its terms and conditions for copying, distributing or modifying 157 | the Program or works based on it. 158 | 6. Each time you redistribute the Program (or any work based on the 159 | Program), the recipient automatically receives a license from the 160 | original licensor to copy, distribute or modify the Program subject to 161 | these terms and conditions. You may not impose any further 162 | restrictions on the recipients' exercise of the rights granted herein. 163 | You are not responsible for enforcing compliance by third parties to 164 | this License. 165 | 7. If, as a consequence of a court judgment or allegation of patent 166 | infringement or for any other reason (not limited to patent issues), 167 | conditions are imposed on you (whether by court order, agreement or 168 | otherwise) that contradict the conditions of this License, they do not 169 | excuse you from the conditions of this License. If you cannot 170 | distribute so as to satisfy simultaneously your obligations under this 171 | License and any other pertinent obligations, then as a consequence you 172 | may not distribute the Program at all. For example, if a patent 173 | license would not permit royalty-free redistribution of the Program by 174 | all those who receive copies directly or indirectly through you, then 175 | the only way you could satisfy both it and this License would be to 176 | refrain entirely from distribution of the Program. 177 | If any portion of this section is held invalid or unenforceable under 178 | any particular circumstance, the balance of the section is intended to 179 | apply and the section as a whole is intended to apply in other 180 | circumstances. 181 | It is not the purpose of this section to induce you to infringe any 182 | patents or other property right claims or to contest validity of any 183 | such claims; this section has the sole purpose of protecting the 184 | integrity of the free software distribution system, which is 185 | implemented by public license practices. Many people have made 186 | generous contributions to the wide range of software distributed 187 | through that system in reliance on consistent application of that 188 | system; it is up to the author/donor to decide if he or she is willing 189 | to distribute software through any other system and a licensee cannot 190 | impose that choice. 191 | This section is intended to make thoroughly clear what is believed to 192 | be a consequence of the rest of this License. 193 | 8. If the distribution and/or use of the Program is restricted in 194 | certain countries either by patents or by copyrighted interfaces, the 195 | original copyright holder who places the Program under this License 196 | may add an explicit geographical distribution limitation excluding 197 | those countries, so that distribution is permitted only in or among 198 | countries not thus excluded. In such case, this License incorporates 199 | the limitation as if written in the body of this License. 200 | 9. The Free Software Foundation may publish revised and/or new versions 201 | of the General Public License from time to time. Such new versions will 202 | be similar in spirit to the present version, but may differ in detail to 203 | address new problems or concerns. 204 | Each version is given a distinguishing version number. If the Program 205 | specifies a version number of this License which applies to it and "any 206 | later version", you have the option of following the terms and conditions 207 | either of that version or of any later version published by the Free 208 | Software Foundation. If the Program does not specify a version number of 209 | this License, you may choose any version ever published by the Free Software 210 | Foundation. 211 | 10. If you wish to incorporate parts of the Program into other free 212 | programs whose distribution conditions are different, write to the author 213 | to ask for permission. For software which is copyrighted by the Free 214 | Software Foundation, write to the Free Software Foundation; we sometimes 215 | make exceptions for this. Our decision will be guided by the two goals 216 | of preserving the free status of all derivatives of our free software and 217 | of promoting the sharing and reuse of software generally. 218 | NO WARRANTY 219 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 220 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 221 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 222 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 223 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 224 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 225 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 226 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 227 | REPAIR OR CORRECTION. 228 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 229 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 230 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 231 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 232 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 233 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 234 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 235 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 236 | POSSIBILITY OF SUCH DAMAGES. 237 | END OF TERMS AND CONDITIONS 238 | How to Apply These Terms to Your New Programs 239 | If you develop a new program, and you want it to be of the greatest 240 | possible use to the public, the best way to achieve this is to make it 241 | free software which everyone can redistribute and change under these terms. 242 | To do so, attach the following notices to the program. It is safest 243 | to attach them to the start of each source file to most effectively 244 | convey the exclusion of warranty; and each file should have at least 245 | the "copyright" line and a pointer to where the full notice is found. 246 | 247 | Copyright (C) 248 | This program is free software; you can redistribute it and/or modify 249 | it under the terms of the GNU General Public License as published by 250 | the Free Software Foundation; either version 2 of the License, or 251 | (at your option) any later version. 252 | This program is distributed in the hope that it will be useful, 253 | but WITHOUT ANY WARRANTY; without even the implied warranty of 254 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 255 | GNU General Public License for more details. 256 | You should have received a copy of the GNU General Public License along 257 | with this program; if not, write to the Free Software Foundation, Inc., 258 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 259 | Also add information on how to contact you by electronic and paper mail. 260 | If the program is interactive, make it output a short notice like this 261 | when it starts in an interactive mode: 262 | Gnomovision version 69, Copyright (C) year name of author 263 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 264 | This is free software, and you are welcome to redistribute it 265 | under certain conditions; type `show c' for details. 266 | The hypothetical commands `show w' and `show c' should show the appropriate 267 | parts of the General Public License. Of course, the commands you use may 268 | be called something other than `show w' and `show c'; they could even be 269 | mouse-clicks or menu items--whatever suits your program. 270 | You should also get your employer (if you work as a programmer) or your 271 | school, if any, to sign a "copyright disclaimer" for the program, if 272 | necessary. Here is a sample; alter the names: 273 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 274 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 275 | , 1 April 1989 276 | Ty Coon, President of Vice 277 | This General Public License does not permit incorporating your program into 278 | proprietary programs. If your program is a subroutine library, you may 279 | consider it more useful to permit linking proprietary applications with the 280 | library. If this is what you want to do, use the GNU Lesser General 281 | Public License instead of this License. 282 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **DEPRECATED** 2 | 3 | On our 2 main projects that used to depend on gutenberg-js, we have moved away and started using the Gutenberg packages directly. 4 | 5 | Please check: 6 | 7 | * https://www.drupal.org/project/gutenberg 8 | * https://github.com/front/g-editor 9 | _____ 10 | 11 | ![gutenberg-js](gutenberg_js_logo.svg?sanitize=true "gutenberg-js") 12 | 13 | # gutenberg-js 14 | 15 | We made [Gutenberg](https://github.com/Wordpress/gutenberg) editor a little more **customizable**! 16 | 17 | Gutenberg editor can **be easily included in your apps** with this [package](https://github.com/front/gutenberg-js). 18 | 19 | This package is based on [Gutenberg v4.8.0](https://github.com/WordPress/gutenberg/releases/tag/v4.8.0) and respective @wordpress packages versions. 20 | 21 | ## Table of contents 22 | 23 | * [Installation](#installation) 24 | * [Dependencies](#dependencies) 25 | * [Development](#development) 26 | * [Global variables](#global-variables) 27 | * [apiFetch](#apifetch) 28 | * [Post Types](#post-types) 29 | * [Wp block](#wp-block) 30 | * [Posts and Pages](#posts-and-pages) 31 | * [Categories](#categories) 32 | * [Media](#media) 33 | * [Taxonomies](#taxonomies) 34 | * [Blocks](#blocks) 35 | * [Themes](#themes) 36 | * [url](#url) 37 | * [Usage](#usage) 38 | * [Gutenberg Stores](#gutenberg-stores) 39 | * [Registering Custom Blocks](#registering-custom-blocks) 40 | * [Customize your Gutenberg](#customize-your-gutenberg) 41 | * [Events](#events) 42 | * [Rendering Dynamic Blocks](#rendering-dynamic-blocks) 43 | * [Custom blocks](#custom-blocks) 44 | * [Creating and Registering](#creating-and-registering) 45 | * [Sharing](#sharing) 46 | 47 | ## Installation 48 | 49 | **gutenberg-js** is available through npm. 50 | 51 | ```sh 52 | $ npm install @frontkom/gutenberg-js 53 | ``` 54 | 55 | [↑ Go up to Table of contents](#table-of-contents) 56 | 57 | ### Dependencies 58 | 59 | Some of the Gutenberg features depend on the [TinyMCE](https://www.tinymce.com/) text editor and the editor expects to find TinyMCE *plugins*, *themes* and *skins* on the project root. Since **gutenberg-js** has TinyMCE as a dependency, we suggest to use webpack and [CopyWebpackPlugin](https://github.com/webpack-contrib/copy-webpack-plugin) to handle with that. 60 | 61 | ```js 62 | // webpack.config.js 63 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 64 | 65 | module.exports = { 66 | ... 67 | plugins: [ 68 | new CopyWebpackPlugin([ 69 | { from: 'node_modules/tinymce/plugins', to: `${ your_root_path }/plugins` }, 70 | { from: 'node_modules/tinymce/themes', to: `${ your_root_path }/themes` }, 71 | { from: 'node_modules/tinymce/skins', to: `${ your_root_path }/skins` }, 72 | ], {}), 73 | ], 74 | ... 75 | } 76 | ``` 77 | 78 | GutenbergJS expects to find React (v16.6.3), ReactDOM (v16.6.3), moment (v2.22.1) and jquery (v1.12.4) libraries in the environment it runs. Maybe you would add the following lines to your pages. 79 | 80 | ```html 81 | 82 | 83 | 84 | 85 | ``` 86 | 87 | [↑ Go up to Table of contents](#table-of-contents) 88 | 89 | ## Development 90 | 91 | The main goal of Gutenberg JS is to expose all Gutenberg packages and keep them up-to-date. 92 | 93 | In order to ensure Gutenberg JS never breaks because of our overrides, we had to use fixed versions for the overrided packages in `package.json`. 94 | 95 | So everytime we have to update Gutenberg JS, there are several steps we must follow: 96 | 97 | 1. Check @wordpress packages versions from Gutenberg release we want to upgrade to and update `package.json` file (`npm outdated` could help). 98 | 2. Check if there are new @wordpress packages and import them in `index.js` file'. 99 | 3. Check if our overrides are updated and work well with new @wordpress packages versions. 100 | 4. Check if there are new blocks containing images and apply `data` attributes override. 101 | 5. Test, test and test. We can use [g-editor](https://github.com/front/g-editor) to test the editor. 102 | 6. [To do: unit tests could help] 103 | 104 | [↑ Go up to Table of contents](#table-of-contents) 105 | 106 | ## Global variables 107 | 108 | Gutenberg depends on several global variables: `wp`, `userSettings`, `wpEditorL10n`, `wpApiSettings`, etc and probably during your Gutenberg experiencie you will discover other required variables, please share with us if you feel they are important to Gutenberg execution. 109 | 110 | Here we're only presenting those variables which - by our experience - we belive are crucial to Gutenberg and already set to them default values. If you don't set them up, you'll see that Gutenberg editor won't run. 111 | 112 | So we recommend you to set up them all in one file called `globals.js` or `settings.js` for example and import them **before** Gutenberg call. Feel free to override Gutenberg global variables if you need. 113 | 114 | ```js 115 | // globals.js 116 | 117 | window.wp = { 118 | apiFetch, 119 | url: { addQueryArgs }, 120 | ..., 121 | }; 122 | 123 | window.userSettings = { 124 | uid: 2, // Among other things, this uid is used to identify and store editor user preferences in localStorage 125 | }; 126 | 127 | // set your root path 128 | window.wpApiSettings = { 129 | root: 'YOUR_ROOT_PATH', 130 | ..., 131 | }; 132 | ``` 133 | 134 | We are working to include on **gutenberg-js** all settings that shouldn't be part of your apps, but you always can override them if you need. 135 | 136 | [↑ Go up to Table of contents](#table-of-contents) 137 | 138 | ### apiFetch 139 | 140 | Those two are very important for communication between the editor and remaining app, so you should set them up according your needs. 141 | 142 | ***apiFetch*** is the method that will handle data operations on Gutenberg, like getting resources (categories for example), saving page changes or deleting pages, etc. It receives an object with `path`, `method`, `data`, etc, so you can treat it as you want. 143 | 144 | ```js 145 | function apiFetch(options) { 146 | // Do something with those options like calling an API 147 | // or actions from your store... 148 | } 149 | ``` 150 | 151 | Next, we will show some commons API requests Gutenberg does and the respective response it expects. For more information, you can check the [WordPress REST API Documentation](https://developer.wordpress.org/rest-api/reference/post-revisions/). 152 | 153 | [↑ Go up to Table of contents](#table-of-contents) 154 | 155 | #### Post Types 156 | 157 | The Gutenberg editor will ask for available **Post Types** through `/wp/v2/types/?context=edit` request. The _type_ properties that can be checked in [WordPress documentation](https://developer.wordpress.org/rest-api/reference/post-types/). 158 | 159 | **Post Types:** _post_, _pages_, _attachment_, _wp_block_ 160 | 161 | **Request for post type settings:** `/wp/v2/types/post?context=edit` 162 | 163 | **Request for page type settings:** `/wp/v2/types/page?context=edit` 164 | 165 | [↑ Go up to Table of contents](#table-of-contents) 166 | 167 | ##### Wp Block 168 | 169 | There is no documentation for `/wp/v2/types/wp_block?context=edit` request yet, but the response should be similar to post and page responses: 170 | 171 | ```js 172 | { 173 | "capabilities": { ... } 174 | "description": "", 175 | "hierarchical": false, 176 | "labels": { ... } 177 | "name": "Blocks", 178 | "slug": "wp_block", 179 | "taxonomies": [], 180 | "rest_base": "blocks", 181 | "supports": { ... } 182 | "viewable": false, 183 | "_links": { ... } 184 | } 185 | ``` 186 | 187 | [↑ Go up to Table of contents](#table-of-contents) 188 | 189 | #### Posts and Pages 190 | 191 | Check the WordPress API documentation for [Posts](https://developer.wordpress.org/rest-api/reference/posts/) and [Pages](https://developer.wordpress.org/rest-api/reference/pages/) requests. 192 | 193 | [↑ Go up to Table of contents](#table-of-contents) 194 | 195 | #### Categories 196 | 197 | Check the WordPress API documentation for [Categories](https://developer.wordpress.org/rest-api/reference/categories/). 198 | 199 | [↑ Go up to Table of contents](#table-of-contents) 200 | 201 | #### Taxonomies 202 | 203 | Taxonomies and Categories are requested to fill Categories panel in Document sidebar. Check the WordPress API documentation for [Taxonomies](https://developer.wordpress.org/rest-api/reference/taxonomies/). 204 | 205 | [↑ Go up to Table of contents](#table-of-contents) 206 | 207 | #### Media 208 | 209 | Here is the WordPress API documentation for [Media](https://developer.wordpress.org/rest-api/reference/media/). The **gutenberg-js** introduces the `data` property which is an object with all data attributes you want to add to image/media DOM element. 210 | 211 | ```js 212 | { 213 | ..., 214 | id: 1527069591355, 215 | link: MEDIA_LINK_HERE, 216 | source_url: MEDIA_URL_HERE, 217 | // Additionaly, you can add some data attributes for images for example 218 | data: { entity_type: 'file', entity_uuid: 'e94e9d8d-4cf4-43c1-b95e-1527069591355' } 219 | ..., 220 | } 221 | ``` 222 | 223 | The editor also requests for `wp/v2/media` OPTIONS: 224 | 225 | ```js 226 | { 227 | headers: { 228 | get: value => { 229 | if (value === 'allow') { 230 | return [ 'POST' ]; 231 | } 232 | }, 233 | }, 234 | } 235 | ``` 236 | 237 | [↑ Go up to Table of contents](#table-of-contents) 238 | 239 | #### Blocks 240 | 241 | There is no documentation for `/wp/v2/wp_blocks` or `/wp/v2/blocks` request yet, but the response should be similar to posts and pages responses with and `id` and `content`: 242 | 243 | ```js 244 | { 245 | content: "

3

", 246 | id: 131, 247 | title: "my block", 248 | } 249 | ``` 250 | 251 | Gutenberg editor allows us to create, edit, list, get one and delete one block operations, so make sure you expect GET, POST, PUT and DELETE requests. 252 | 253 | [↑ Go up to Table of contents](#table-of-contents) 254 | 255 | #### Themes 256 | 257 | Gutenberg will ask for the [theme features](https://codex.wordpress.org/Theme_Features) through the index request (`/wp/v2/themes`). The response should be the following object. 258 | 259 | ```js 260 | { 261 | ..., 262 | theme_supports: { 263 | formats: [ 'standard', 'aside', 'image', 'video', 'quote', 'link', 'gallery', 'audio' ], 264 | 'post-thumbnails': true, 265 | }, 266 | ..., 267 | } 268 | ``` 269 | 270 | [↑ Go up to Table of contents](#table-of-contents) 271 | 272 | ### url 273 | 274 | ***url*** should has a function called `addQueryArgs( url, args )` that handles with `url` and `args` and returns the final url to different actions. The original implementation is the following, feel free to keep it or change it according to your needs. 275 | 276 | ```js 277 | /** 278 | * External dependencies 279 | */ 280 | import { parse, format } from 'url'; 281 | import { parse as parseQueryString, stringify } from 'querystring'; 282 | 283 | /** 284 | * Appends arguments to the query string of the url 285 | * 286 | * @param {String} url URL 287 | * @param {Object} args Query Args 288 | * 289 | * @return {String} Updated URL 290 | */ 291 | export function addQueryArgs(url, args) { 292 | const queryStringIndex = url.indexOf('?'); 293 | const query = queryStringIndex !== -1 ? parse(url.substr(queryStringIndex + 1)) : {}; 294 | const baseUrl = queryStringIndex !== -1 ? url.substr(0, queryStringIndex) : url; 295 | 296 | return baseUrl + '?' + stringify({ ...query, ...args }); 297 | } 298 | ``` 299 | 300 | [↑ Go up to Table of contents](#table-of-contents) 301 | 302 | ## Usage 303 | 304 | We've tried to make it easy to import **gutenberg-js** modules to your apps. 305 | 306 | ```js 307 | // Importing global variables that Gutenberg requires 308 | import './globals'; 309 | 310 | // Importing domReady and editPost modules 311 | import { domReady, editPost } from '@frontkom/gutenberg-js'; 312 | 313 | // Don't forget to import the style 314 | import '@frontkom/gutenberg-js/build/css/block-library/style.css'; 315 | import '@frontkom/gutenberg-js/build/css/style.css'; 316 | 317 | // DOM element id where editor will be displayed 318 | const target = 'editor'; 319 | 320 | // Post properties 321 | const postType = 'post'; // or 'page' 322 | const postId = 123; 323 | 324 | // Some editor settings 325 | const settings = { 326 | alignWide: true, 327 | availableTemplates: [], 328 | allowedBlockTypes: true, 329 | disableCustomColors: false, 330 | disableCustomFontSizes: false, 331 | disablePostFormats: false, 332 | titlePlaceholder: "Add title", 333 | bodyPlaceholder: "Write your story", 334 | isRTL: false, 335 | autosaveInterval: 10, 336 | styles: [], 337 | postLock: { 338 | isLocked: false, 339 | }, 340 | ... 341 | // @frontkom/gutenberg-js settings 342 | canAutosave: false, // to disable the Editor Autosave feature (default: true) 343 | canPublish: false, // to disable the Editor Publish feature (default: true) 344 | canSave: false, // to disable the Editor Save feature (default: true) 345 | mediaLibrary: false, // to disable the Media Library feature (default: true) 346 | }; 347 | 348 | // Post properties to override 349 | const overridePost = {}; 350 | 351 | // Et voilá... Initializing the editor! 352 | window._wpLoadGutenbergEditor = new Promise(function (resolve) { 353 | domReady(function () { 354 | resolve(editPost.initializeEditor(target, postType, postId, settings, overridePost)); 355 | }); 356 | }); 357 | ``` 358 | 359 | **Note**: Gutenberg requires utf-8 encoding, so don't forget to add `` tag to your html ``. 360 | 361 | [↑ Go up to Table of contents](#table-of-contents) 362 | 363 | ### Gutenberg Stores 364 | 365 | Additionally, after initializing the editor, you can have access to Gutenberg stores (`core`, `core/blocks`, `core/data`, `core/edit-post`, `core/editor`, `core/viewport`) through the `data` module and its `select` and `dispatch` methods: 366 | 367 | ```js 368 | // Importing select and dispatch methods from @frontkom/gutenberg-js package 369 | import { data } from '@frontkom/gutenberg-js'; 370 | 371 | // Use dispatch to change the state of something 372 | data.dispatch('core/edit-post').openGeneralSidebar('edit-post/block'); 373 | data.dispatch('core/edit-post').closeGeneralSidebar(); 374 | 375 | // Use select to get the state of something 376 | data.select( 'core/editor' ).getEditedPostContent(); 377 | // 378 | //

Hello

379 | // 380 | 381 | ``` 382 | 383 | [↑ Go up to Table of contents](#table-of-contents) 384 | 385 | ### Registering Custom Blocks 386 | 387 | You can create your custom blocks using the `registerBlockType` method from `blocks` module. Check out the example below and the Wordpress [documentation](https://wordpress.org/gutenberg/handbook/blocks/) to read more about it. 388 | 389 | ```js 390 | import { blocks, editor } from '@frontkom/gutenberg-js'; 391 | 392 | const { 393 | AlignmentToolbar, 394 | BlockControls, 395 | RichText, 396 | } = editor; 397 | 398 | blocks.registerBlockType('custom/my-block', { 399 | title: 'My first block', 400 | icon: 'universal-access-alt', 401 | category: 'common', 402 | attributes: { 403 | content: { 404 | type: 'array', 405 | source: 'children', 406 | selector: 'p', 407 | }, 408 | alignment: { 409 | type: 'string', 410 | }, 411 | }, 412 | edit({ attributes, className, setAttributes }) { 413 | const { content, alignment } = attributes; 414 | 415 | function onChangeContent( newContent ) { 416 | setAttributes( { content: newContent } ); 417 | } 418 | 419 | function onChangeAlignment( newAlignment ) { 420 | setAttributes( { alignment: newAlignment } ); 421 | } 422 | 423 | return [ 424 | 425 | 429 | , 430 | 437 | ]; 438 | }, 439 | save({ attributes, className }) { 440 | const { content, alignment } = attributes; 441 | 442 | return ( 443 | 448 | ); 449 | }, 450 | }); 451 | 452 | ``` 453 | 454 | [↑ Go up to Table of contents](#table-of-contents) 455 | 456 | ## Customize your Gutenberg 457 | 458 | Following the same logic, we've created the `customGutenberg` global object where you can set everything that we made customizable on Gutenberg. 459 | 460 | ```js 461 | window.customGutenberg = { ... }; 462 | ``` 463 | 464 | As the other global variables, `customGutenberg` should be defined **before** Gutenberg import. 465 | 466 | Important to say that Gutenberg works perfectly without the settings of this object :) 467 | 468 | [↑ Go up to Table of contents](#table-of-contents) 469 | 470 | ### Events 471 | 472 | **gutenberg-js** makes possible to define callbacks (or effects) for Gutenberg actions. Since it is an experimental feature, we are only providing this for 'OPEN_GENERAL_SIDEBAR' and 'CLOSE_GENERAL_SIDEBAR' actions. 473 | 474 | ```js 475 | window.customGutenberg = { 476 | ..., 477 | events: { 478 | 'OPEN_GENERAL_SIDEBAR': function(action, store) { 479 | console.log( 'OPEN_GENERAL_SIDEBAR', action, store ); 480 | }, 481 | 'CLOSE_GENERAL_SIDEBAR': function(action, store) { 482 | console.log( 'CLOSE_GENERAL_SIDEBAR', action, store ); 483 | }, 484 | }, 485 | ..., 486 | }; 487 | ``` 488 | 489 | [↑ Go up to Table of contents](#table-of-contents) 490 | 491 | ## Rendering Dynamic Blocks 492 | 493 | As you probably know, Gutenberg allows us to create our owns blocks inside the editor and make them reusable. We can look for reusable blocks in the 'Add Blocks Menu' search bar. 494 | 495 | If we change to 'Code Editor' mode, we can check that only a `ref` id is saved for our reusable block. 496 | 497 | ```js 498 | 499 | ``` 500 | 501 | Gutenberg uses `wp/v2/wp_blocks/[:id]` request to get the block content inside the editor. Make sure you do the same process when your app do the final render of the page (outside of the editor). 502 | 503 | The same happens with embed blocks: 504 | 505 | ```js 506 | 507 |
508 |
509 | https://twitter.com/drupalgutenberg/status/1040203765452820480 510 |
511 |
512 | 513 | ``` 514 | 515 | And latest posts widget: 516 | 517 | ```js 518 | 519 | ``` 520 | 521 | Your app must be in charge of the render of the dynamic blocks. 522 | 523 | [↑ Go up to Table of contents](#table-of-contents) 524 | 525 | ## Custom Blocks 526 | 527 | We can create **custom blocks** to our Gutenberg editor and used them to build our website pages. 528 | 529 | [↑ Go up to Table of contents](#table-of-contents) 530 | 531 | ### Creating and Registering 532 | 533 | A Gutenberg block requires some properties like a `title`, an `icon`, a `category` and the `edit` and the `save` methods which describe the structure of the block inside the editor and what block content should be saved. 534 | 535 | ```js 536 | const myFirstBlock = { 537 | title: 'My first block!', 538 | icon: 'universal-access-alt', 539 | category: 'cloudblocks', 540 | 541 | edit() { 542 | return

Hello editor.

; 543 | }, 544 | 545 | save() { 546 | return

Hello saved content.

; 547 | }, 548 | }; 549 | ``` 550 | 551 | After defining all the properties, the new block must be registered so it becomes available in editor inserter dialog under the chosen category. If the blocks's category doesn't exist yet, we must add it to the editor inserter dialog. 552 | 553 | A blocks category requires a slug and a title: 554 | 555 | ```js 556 | const category = { 557 | slug: 'cloudblocks', 558 | title: 'Gutenberg-Cloud Blocks', 559 | }; 560 | ``` 561 | 562 | To check which categories already exist, we can use `getCategories()` selector and to add a new category to the editor we can use `setCategories()` action. Both methods are provided by Gutenberg `core/blocks` store which are accessible througg `wp.data`. 563 | 564 | ```js 565 | const { dispatch, select } = wp.data; 566 | 567 | const currentCategories = select('core/blocks').getCategories().filter(item => item.slug !== category.slug); 568 | 569 | dispatch('core/blocks').setCategories([ category, ...currentCategories ]); 570 | ``` 571 | 572 | Finally, we are ready to register our custom block using `registerBlockType` method: 573 | 574 | ```js 575 | const { registerBlockType } = wp.blocks; 576 | 577 | registerBlockType(`${category.slug}/my-first-block`, { category: category.slug, ...hero.settings }); 578 | ``` 579 | 580 | And the block is available in the editor inserter dialog! Full example: 581 | 582 | ```js 583 | const { dispatch, select } = wp.data; 584 | const { registerBlockType } = wp.blocks; 585 | 586 | // Setting block's properties 587 | const myFirstBlock = { 588 | title: 'My first block!', 589 | icon: 'universal-access-alt', 590 | category: 'cloudblocks', 591 | 592 | edit() { 593 | return

Hello editor.

; 594 | }, 595 | 596 | save() { 597 | return

Hello saved content.

; 598 | }, 599 | }; 600 | 601 | // Setting category's properties 602 | const category = { 603 | slug: 'cloudblocks', 604 | title: 'Gutenberg-Cloud Blocks', 605 | }; 606 | 607 | // Checking the category 608 | const currentCategories = select('core/blocks').getCategories().filter(item => item.slug !== category.slug); 609 | dispatch('core/blocks').setCategories([ category, ...currentCategories ]); 610 | 611 | // Registering the new block 612 | registerBlockType(`${category.slug}/my-first-block`, myFirstBlock); 613 | ``` 614 | 615 | In [Creating Block Types](https://wordpress.org/gutenberg/handbook/blocks/) section of Gutenberg handbook, we can check more examples of how to custom blocks with more complexity. Also we can check more details about blocks properties in [Block API](https://wordpress.org/gutenberg/handbook/block-api/) documentation. 616 | 617 | [↑ Go up to Table of contents](#table-of-contents) 618 | 619 | ### Sharing 620 | 621 | An easy way to share a custom block is to publish the block as a npm package. 622 | 623 | Here is an example of a custom block npm package, the [Hero Section](https://github.com/front/g-hero-section). 624 | 625 | [↑ Go up to Table of contents](#table-of-contents) 626 | -------------------------------------------------------------------------------- /gutenberg_js_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /languages/README.md: -------------------------------------------------------------------------------- 1 | After the build completes, you'll find a `gutenberg.pot` strings file in this directory, which is dispensable. 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@frontkom/gutenberg-js", 3 | "version": "4.0.1", 4 | "description": "gutenberg-js - An extension of the Wordpress Gutenberg editor", 5 | "main": "build/js/gutenberg-js.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/front/gutenberg-js.git" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "build": "NODE_ENV=production webpack", 13 | "dev": "NODE_ENV=development webpack", 14 | "lint": "eslint .", 15 | "lint:fix": "eslint . --fix", 16 | "deploy": "npm run lint && npm publish --access public" 17 | }, 18 | "homepage": "https://github.com/front/gutenberg-js#readme", 19 | "bugs": { 20 | "url": "https://github.com/front/gutenberg-js/issues" 21 | }, 22 | "author": "The Frontkom Contributors", 23 | "license": "GPL-2.0-or-later", 24 | "keywords": [ 25 | "CMS", 26 | "Drupal", 27 | "Editor", 28 | "Frontkom", 29 | "Gutenberg", 30 | "Page builder", 31 | "WordPress", 32 | "WYSIWYG" 33 | ], 34 | "dependencies": { 35 | "@wordpress/a11y": "^2.0.2", 36 | "@wordpress/api-fetch": "2.2.8", 37 | "@wordpress/autop": "^2.0.2", 38 | "@wordpress/blob": "^2.1.0", 39 | "@wordpress/block-library": "^2.2.13", 40 | "@wordpress/block-serialization-default-parser": "^2.0.4", 41 | "@wordpress/blocks": "^6.0.5", 42 | "@wordpress/components": "^7.0.6", 43 | "@wordpress/compose": "^3.0.1", 44 | "@wordpress/core-data": "^2.0.17", 45 | "@wordpress/data": "^4.2.1", 46 | "@wordpress/date": "^3.0.1", 47 | "@wordpress/deprecated": "^2.0.5", 48 | "@wordpress/dom": "^2.0.8", 49 | "@wordpress/dom-ready": "^2.0.2", 50 | "@wordpress/edit-post": "3.1.8", 51 | "@wordpress/editor": "9.0.8", 52 | "@wordpress/element": "^2.1.9", 53 | "@wordpress/escape-html": "^1.0.1", 54 | "@wordpress/format-library": "^1.2.11", 55 | "@wordpress/hooks": "^2.0.5", 56 | "@wordpress/html-entities": "^2.0.4", 57 | "@wordpress/i18n": "^3.1.1", 58 | "@wordpress/is-shallow-equal": "^1.1.5", 59 | "@wordpress/keycodes": "^2.0.6", 60 | "@wordpress/notices": "1.1.3", 61 | "@wordpress/nux": "^3.0.7", 62 | "@wordpress/plugins": "^2.0.10", 63 | "@wordpress/redux-routine": "^3.0.4", 64 | "@wordpress/rich-text": "^3.0.5", 65 | "@wordpress/shortcode": "^2.0.2", 66 | "@wordpress/token-list": "^1.1.0", 67 | "@wordpress/url": "2.3.3", 68 | "@wordpress/viewport": "^2.1.1", 69 | "@wordpress/wordcount": "^2.0.3", 70 | "tinymce": "4.9.2" 71 | }, 72 | "devDependencies": { 73 | "@babel/core": "^7.2.2", 74 | "@babel/preset-react": "^7.0.0", 75 | "@wordpress/eslint-plugin": "^1.0.1", 76 | "babel-eslint": "^10.0.1", 77 | "babel-loader": "^8.0.5", 78 | "clean-webpack-plugin": "^1.0.0", 79 | "eslint": "^5.9.0", 80 | "eslint-plugin-jest": "^22.1.3", 81 | "eslint-plugin-jsx-a11y": "^6.1.0", 82 | "eslint-plugin-react": "^7.10.0", 83 | "extract-text-webpack-plugin": "4.0.0-beta.0", 84 | "node-sass": "^4.11.0", 85 | "path-replace-loader": "^2.0.0", 86 | "sass-loader": "^7.1.0", 87 | "string-replace-webpack-plugin": "^0.1.3", 88 | "webpack": "^4.28.4", 89 | "webpack-cli": "^3.2.1" 90 | }, 91 | "private": false 92 | } 93 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@wordpress/eslint-plugin/recommended", 4 | "plugin:jest/recommended" 5 | ], 6 | "rules": { 7 | "prefer-rest-params": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/edit-post/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # edit-post changelog 2 | 3 | ## 3.0.0 4 | 5 | ### Added 6 | 7 | - [packages/edit-post](https://github.com/front/gutenberg-js/blob/v2.7.0/src/js/gutenberg-overrides/packages/edit-post) overrides 8 | 9 | - `addQueryArgs` to 'Manage All Reusable Blocks' link (ToolsMoreMenuGroup) ([packages/edit-post/build-module/plugins/index.js](https://github.com/front/gutenberg-js/blob/v2.7.0/src/js/gutenberg-overrides/packages/edit-post/build-module/plugins/index.js)) 10 | 11 | - `OPEN_GENERAL_SIDEBAR` and `CLOSE_GENERAL_SIDEBAR` effects [packages/edit-post/build-module/store/effects.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/packages/edit-post/build-module/store/effects.js)) 12 | 13 | ### Changed 14 | 15 | - **MediaUpload** component and implement the Media Library with existing images [packages/edit-post/build-module/hooks/components/media-upload/index.js](https://github.com/front/gutenberg-js/blob/v2.7.0/src/js/gutenberg-overrides/packages/edit-post/build-module/hooks/components/media-upload/index.js)) 16 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/edit-post/build-module/hooks/components/media-upload/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { Component, Fragment } from 'react'; 5 | import { get } from 'lodash'; 6 | 7 | /** 8 | * WordPress dependencies 9 | */ 10 | import { __ } from '@wordpress/i18n'; 11 | import { Popover } from '@wordpress/components'; 12 | import { withSelect } from '@wordpress/data'; 13 | 14 | class MediaContainer extends Component { 15 | constructor( props ) { 16 | super( props ); 17 | 18 | this.onImageClick = this.onImageClick.bind( this ); 19 | } 20 | 21 | onImageClick( img ) { 22 | const { onSelect, closePopover, gallery = false, multiple = false } = this.props; 23 | 24 | const imgObject = { 25 | alt: img.alt_text, 26 | caption: img.caption.raw, 27 | id: img.id, 28 | link: img.link, 29 | mime: img.mime_type, 30 | sizes: img.media_details.sizes, 31 | subtype: img.mime_type.split( '/' )[ 1 ], 32 | type: img.mime_type.split( '/' )[ 0 ], 33 | url: img.source_url, 34 | data: img.data, 35 | }; 36 | 37 | if ( gallery || multiple ) { 38 | onSelect( [ imgObject ] ); 39 | } else { 40 | onSelect( imgObject ); 41 | } 42 | 43 | closePopover(); 44 | } 45 | 46 | render() { 47 | const { media, allowedTypes = [] } = this.props; 48 | const items = media && media.filter( ( item ) => ! allowedTypes.length || allowedTypes.includes( item.media_type ) ); 49 | return ( 50 |
51 | { items && items.map( ( item ) => { 52 | const sourceUrl = get( item, 'media_details.sizes.thumbnail.source_url' ) || 53 | ( item.media_type === 'image' && item.source_url ); 54 | const buttonStyle = sourceUrl ? { backgroundImage: `url(${ sourceUrl })` } : {}; 55 | 56 | return ; 62 | } ) } 63 |
64 | ); 65 | } 66 | } 67 | 68 | const MediaLibrary = withSelect( ( select ) => ( { 69 | media: select( 'core' ).getMediaItems(), 70 | } ) )( MediaContainer ); 71 | 72 | class MediaUpload extends Component { 73 | constructor( props ) { 74 | super( props ); 75 | this.state = { isVisible: false }; 76 | 77 | this.openPopover = this.openPopover.bind( this ); 78 | this.closePopover = this.closePopover.bind( this ); 79 | } 80 | 81 | openPopover() { 82 | this.setState( { isVisible: true } ); 83 | } 84 | 85 | closePopover() { 86 | this.setState( { isVisible: false } ); 87 | } 88 | 89 | render() { 90 | if ( ! this.props.mediaLibrary ) { 91 | // console.log('Media Library is deactivated'); 92 | return false; 93 | } 94 | 95 | const { isVisible } = this.state; 96 | 97 | return 98 | { isVisible && 99 | event.stopPropagation() } 103 | position="middle left" 104 | headerTitle={ __( 'Media Library' ) } 105 | > 106 | 110 | 111 | } 112 | { this.props.render( { open: this.openPopover } ) } 113 | ; 114 | } 115 | } 116 | 117 | export default withSelect( ( select ) => ( { 118 | mediaLibrary: select( 'core/editor' ).getEditorSettings().mediaLibrary, 119 | } ) )( MediaUpload ); 120 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/edit-post/build-module/plugins/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { MenuItem } from '@wordpress/components'; 5 | import { Fragment } from '@wordpress/element'; 6 | import { __ } from '@wordpress/i18n'; 7 | import { registerPlugin } from '@wordpress/plugins'; 8 | 9 | // GUTENBERG JS - use addQueryArgs instead of hard coding 10 | // 'edit.php?post_type=wp_block' 11 | import { addQueryArgs } from '@wordpress/url'; 12 | 13 | /** 14 | * Internal dependencies 15 | */ 16 | import CopyContentMenuItem from './copy-content-menu-item'; 17 | import KeyboardShortcutsHelpMenuItem from './keyboard-shortcuts-help-menu-item'; 18 | import ToolsMoreMenuGroup from '../components/header/tools-more-menu-group'; 19 | 20 | registerPlugin( 'edit-post', { 21 | render() { 22 | return ( 23 | 24 | 25 | { ( { onClose } ) => ( 26 | 27 | 31 | { __( 'Manage All Reusable Blocks' ) } 32 | 33 | 34 | 35 | 36 | ) } 37 | 38 | 39 | ); 40 | }, 41 | } ); 42 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/edit-post/build-module/store/effects.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { get } from 'lodash'; 5 | import def from '@wordpress/edit-post/build-module/store/effects?source=node_modules'; 6 | 7 | def.OPEN_GENERAL_SIDEBAR = ( action, store ) => { 8 | if ( get( window, [ 'customGutenberg', 'events', 'OPEN_GENERAL_SIDEBAR' ] ) ) { 9 | window.customGutenberg.events.OPEN_GENERAL_SIDEBAR( action, store ); 10 | } 11 | }; 12 | 13 | def.CLOSE_GENERAL_SIDEBAR = ( action, store ) => { 14 | if ( get( window, [ 'customGutenberg', 'events', 'CLOSE_GENERAL_SIDEBAR' ] ) ) { 15 | window.customGutenberg.events.CLOSE_GENERAL_SIDEBAR( action, store ); 16 | } 17 | }; 18 | 19 | export default def; 20 | export * from '@wordpress/edit-post/build-module/store/effects?source=node_modules'; 21 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/editor/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # editor changelog 2 | 3 | ## 4.0.0 4 | 5 | ### Changed 6 | 7 | - Moved 'post-publish-panel' override from toggle.js to index.js. `PostPublishPanelToggle` has been deprecated and no longer exists. 8 | 9 | ## 3.0.0 10 | 11 | ### Removed 12 | 13 | - BlockDropZone override 14 | 15 | - BlockListBlock override 16 | 17 | - `insertDefaultBlock` action override 18 | 19 | - mediaUpload override 20 | 21 | ### Added 22 | 23 | - 'post-publish-button' override 24 | 25 | - 'post-preview-button' override 26 | 27 | - `addQueryArgs` to 'Manage All Reusable Blocks' link (Inserter Menu) ([packages/editor/build-module/components/inserter/menu.js](https://github.com/front/gutenberg-js/blob/v2.7.0/src/js/gutenberg-overrides/packages/editor/build-module/components/inserter/menu.js)) 28 | 29 | ## 2.5.0 2018-09-19 30 | 31 | ### Changed 32 | 33 | - media-upload overrides according gutenberg 3.8.0 and moved it to `packages/editor/build-module/utils/media-upload/media-upload.js` 34 | 35 | ## 2.0.0 2018-08-30 36 | 37 | ### Changed 38 | 39 | - editor overrides to `packages/editor/build-module` folder 40 | 41 | ## 1.2.0 2018-07-28 42 | 43 | ### Changed 44 | 45 | - `onDrop` adding a workaround (`checkEditor`) to check if the editor was initialized before set the content ([editor/components/rich-text/index.js](https://github.com/front/gutenberg-js/blob/v1.2.0/src/js/gutenberg-overrides/editor/components/rich-text/index.js)) 46 | 47 | - `onPastePreProcess` checking if is a dropped post (with attributes) ([editor/components/rich-text/index.js](https://github.com/front/gutenberg-js/blob/v1.2.0/src/js/gutenberg-overrides/editor/components/rich-text/index.js)) 48 | 49 | ## 1.1.0 2018-07-27 50 | 51 | ### Added 52 | 53 | - `ui` prop in BlockDropZone call ([editor/components/block-list/block.js](https://github.com/front/gutenberg-js/blob/v1.1.0/src/js/gutenberg-overrides/editor/components/block-list/block.js)) 54 | 55 | ### Changed 56 | 57 | - `blocks` to `attribute` and `updateBlockAttributes` instead of insert a new one, in `onDrop` ([editor/components/block-drop-zone/index.js](https://github.com/front/gutenberg-js/blob/v1.1.0/src/js/gutenberg-overrides/editor/components/block-drop-zone/index.js)) 58 | 59 | ## 0.0.1 2018-07-11 60 | 61 | ### Added 62 | 63 | - `canPublish`, `canSave` and `canAutosave` properties and default values to `EDITOR_SETTINGS_DEFAULTS` ([editor/store/defaults.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/editor/store/defaults.js)) 64 | 65 | - `insertDefaultBlock` override ([editor/store/actions.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/editor/store/actions.js)) 66 | 67 | - `ifCondition` to **AutosaveMonitor**, controlled by `canSave` and `canAutosave` settings ([editor/components/autosave-monitor/index.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/editor/components/autosave-monitor/index.js)) 68 | 69 | - `ifCondition` to **PostPublishPanelToggle**, controlled by `canPublish` setting ([editor/components/post-publish-panel/toggle.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/editor/components/post-publish-panel/toggle.js)) 70 | 71 | - `ifCondition` to **PostSavedState**, controlled by `canSave` setting ([editor/components/post-saved-state/index.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/editor/components/post-saved-state/index.js)) 72 | 73 | - `setContent` override (commented `bookmark` workaround) ([editor/components/rich-text/index.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/editor/components/rich-text/index.js)) 74 | 75 | - `onDrop` override in order to accept blocks from PostItemDraggable ([editor/components/block-drop-zone/index.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/editor/components/block-drop-zone/index.js)) 76 | 77 | - `INSERTER_UTILITY_HIGH`, `INSERTER_UTILITY_MEDIUM` and `INSERTER_UTILITY_LOW` overrides with `INSERTER_UTILITY_NONE` so there is no different levels of utility and consequently no **Most Used** panel ([editor/store/selectors.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/editor/store/selectors.js)) 78 | 79 | - `data` property to `mediaObject` and use `get` function to obtain image `title` in `mediaUpload` function ([utils/mediaupload.js](https://github.com/front/gutenberg-js/blob/v0.0.1/src/js/gutenberg-overrides/utils/mediaupload.js)) 80 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/editor/build-module/components/autosave-monitor/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { Component } from '@wordpress/element'; 5 | import { compose, ifCondition } from '@wordpress/compose'; 6 | import { withSelect, withDispatch } from '@wordpress/data'; 7 | 8 | export class AutosaveMonitor extends Component { 9 | componentDidUpdate( prevProps ) { 10 | const { isDirty, editsReference, isAutosaveable } = this.props; 11 | 12 | if ( 13 | prevProps.isDirty !== isDirty || 14 | prevProps.isAutosaveable !== isAutosaveable || 15 | prevProps.editsReference !== editsReference 16 | ) { 17 | this.toggleTimer( isDirty && isAutosaveable ); 18 | } 19 | } 20 | 21 | componentWillUnmount() { 22 | this.toggleTimer( false ); 23 | } 24 | 25 | toggleTimer( isPendingSave ) { 26 | clearTimeout( this.pendingSave ); 27 | const { autosaveInterval } = this.props; 28 | if ( isPendingSave ) { 29 | this.pendingSave = setTimeout( 30 | () => this.props.autosave(), 31 | autosaveInterval * 1000 32 | ); 33 | } 34 | } 35 | 36 | render() { 37 | return null; 38 | } 39 | } 40 | 41 | export default compose( [ 42 | withSelect( ( select ) => { 43 | const { 44 | isEditedPostDirty, 45 | isEditedPostAutosaveable, 46 | getEditorSettings, 47 | getReferenceByDistinctEdits, 48 | } = select( 'core/editor' ); 49 | 50 | const { autosaveInterval, canSave, canAutosave } = getEditorSettings(); 51 | 52 | return { 53 | isDirty: isEditedPostDirty(), 54 | isAutosaveable: isEditedPostAutosaveable(), 55 | editsReference: getReferenceByDistinctEdits(), 56 | autosaveInterval, 57 | 58 | // GUTENBERG JS 59 | canSave, 60 | canAutosave, 61 | }; 62 | } ), 63 | withDispatch( ( dispatch ) => ( { 64 | autosave: dispatch( 'core/editor' ).autosave, 65 | } ) ), 66 | 67 | // GUTENBERG JS 68 | // added the ifCondition to enable/disable 69 | // the autoave feature according 'canSave' and 'canAutosave' settings 70 | ifCondition( ( { canSave, canAutosave } ) => canSave && canAutosave ), 71 | ] )( AutosaveMonitor ); 72 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/editor/build-module/components/inserter/menu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { 5 | filter, 6 | find, 7 | findIndex, 8 | flow, 9 | groupBy, 10 | isEmpty, 11 | map, 12 | some, 13 | sortBy, 14 | without, 15 | includes, 16 | deburr, 17 | } from 'lodash'; 18 | import scrollIntoView from 'dom-scroll-into-view'; 19 | 20 | /** 21 | * WordPress dependencies 22 | */ 23 | import { __, _n, _x, sprintf } from '@wordpress/i18n'; 24 | import { Component, createRef } from '@wordpress/element'; 25 | import { withSpokenMessages, PanelBody } from '@wordpress/components'; 26 | import { 27 | getCategories, 28 | isReusableBlock, 29 | createBlock, 30 | isUnmodifiedDefaultBlock, 31 | } from '@wordpress/blocks'; 32 | import { withDispatch, withSelect } from '@wordpress/data'; 33 | import { withInstanceId, compose, withSafeTimeout } from '@wordpress/compose'; 34 | import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; 35 | 36 | // GUTENBERG JS - use addQueryArgs instead of hard coding 37 | // 'edit.php?post_type=wp_block' 38 | import { addQueryArgs } from '@wordpress/url'; 39 | 40 | /** 41 | * Internal dependencies 42 | */ 43 | import BlockPreview from '../block-preview'; 44 | import BlockTypesList from '../block-types-list'; 45 | import ChildBlocks from './child-blocks'; 46 | import InserterInlineElements from './inline-elements'; 47 | 48 | const MAX_SUGGESTED_ITEMS = 9; 49 | 50 | const stopKeyPropagation = ( event ) => event.stopPropagation(); 51 | 52 | /** 53 | * Filters an item list given a search term. 54 | * 55 | * @param {Array} items Item list 56 | * @param {string} searchTerm Search term. 57 | * 58 | * @return {Array} Filtered item list. 59 | */ 60 | export const searchItems = ( items, searchTerm ) => { 61 | const normalizedSearchTerm = normalizeTerm( searchTerm ); 62 | const matchSearch = ( string ) => normalizeTerm( string ).indexOf( normalizedSearchTerm ) !== -1; 63 | const categories = getCategories(); 64 | 65 | return items.filter( ( item ) => { 66 | const itemCategory = find( categories, { slug: item.category } ); 67 | return matchSearch( item.title ) || some( item.keywords, matchSearch ) || ( itemCategory && matchSearch( itemCategory.title ) ); 68 | } ); 69 | }; 70 | 71 | /** 72 | * Converts the search term into a normalized term. 73 | * 74 | * @param {string} term The search term to normalize. 75 | * 76 | * @return {string} The normalized search term. 77 | */ 78 | export const normalizeTerm = ( term ) => { 79 | // Disregard diacritics. 80 | // Input: "média" 81 | term = deburr( term ); 82 | 83 | // Accommodate leading slash, matching autocomplete expectations. 84 | // Input: "/media" 85 | term = term.replace( /^\//, '' ); 86 | 87 | // Lowercase. 88 | // Input: "MEDIA" 89 | term = term.toLowerCase(); 90 | 91 | // Strip leading and trailing whitespace. 92 | // Input: " media " 93 | term = term.trim(); 94 | 95 | return term; 96 | }; 97 | 98 | export class InserterMenu extends Component { 99 | constructor() { 100 | super( ...arguments ); 101 | this.state = { 102 | childItems: [], 103 | filterValue: '', 104 | hoveredItem: null, 105 | suggestedItems: [], 106 | reusableItems: [], 107 | itemsPerCategory: {}, 108 | openPanels: [ 'suggested' ], 109 | }; 110 | this.onChangeSearchInput = this.onChangeSearchInput.bind( this ); 111 | this.onHover = this.onHover.bind( this ); 112 | this.panels = {}; 113 | this.inserterResults = createRef(); 114 | } 115 | 116 | componentDidMount() { 117 | // This could be replaced by a resolver. 118 | this.props.fetchReusableBlocks(); 119 | this.filter(); 120 | } 121 | 122 | componentDidUpdate( prevProps ) { 123 | if ( prevProps.items !== this.props.items ) { 124 | this.filter( this.state.filterValue ); 125 | } 126 | } 127 | 128 | onChangeSearchInput( event ) { 129 | this.filter( event.target.value ); 130 | } 131 | 132 | onHover( item ) { 133 | this.setState( { 134 | hoveredItem: item, 135 | } ); 136 | 137 | const { showInsertionPoint, hideInsertionPoint } = this.props; 138 | if ( item ) { 139 | const { rootClientId, index } = this.props; 140 | showInsertionPoint( rootClientId, index ); 141 | } else { 142 | hideInsertionPoint(); 143 | } 144 | } 145 | 146 | bindPanel( name ) { 147 | return ( ref ) => { 148 | this.panels[ name ] = ref; 149 | }; 150 | } 151 | 152 | onTogglePanel( panel ) { 153 | return () => { 154 | const isOpened = this.state.openPanels.indexOf( panel ) !== -1; 155 | if ( isOpened ) { 156 | this.setState( { 157 | openPanels: without( this.state.openPanels, panel ), 158 | } ); 159 | } else { 160 | this.setState( { 161 | openPanels: [ 162 | ...this.state.openPanels, 163 | panel, 164 | ], 165 | } ); 166 | 167 | this.props.setTimeout( () => { 168 | // We need a generic way to access the panel's container 169 | // eslint-disable-next-line react/no-find-dom-node 170 | scrollIntoView( this.panels[ panel ], this.inserterResults.current, { 171 | alignWithTop: true, 172 | } ); 173 | } ); 174 | } 175 | }; 176 | } 177 | 178 | filter( filterValue = '' ) { 179 | const { debouncedSpeak, items, rootChildBlocks } = this.props; 180 | const filteredItems = searchItems( items, filterValue ); 181 | 182 | const childItems = filter( filteredItems, ( { name } ) => includes( rootChildBlocks, name ) ); 183 | 184 | let suggestedItems = []; 185 | if ( ! filterValue ) { 186 | const maxSuggestedItems = this.props.maxSuggestedItems || MAX_SUGGESTED_ITEMS; 187 | suggestedItems = filter( items, ( item ) => item.utility > 0 ).slice( 0, maxSuggestedItems ); 188 | } 189 | 190 | const reusableItems = filter( filteredItems, { category: 'reusable' } ); 191 | 192 | const getCategoryIndex = ( item ) => { 193 | return findIndex( getCategories(), ( category ) => category.slug === item.category ); 194 | }; 195 | const itemsPerCategory = flow( 196 | ( itemList ) => filter( itemList, ( item ) => item.category !== 'reusable' ), 197 | ( itemList ) => sortBy( itemList, getCategoryIndex ), 198 | ( itemList ) => groupBy( itemList, 'category' ) 199 | )( filteredItems ); 200 | 201 | let openPanels = this.state.openPanels; 202 | if ( filterValue !== this.state.filterValue ) { 203 | if ( ! filterValue ) { 204 | openPanels = [ 'suggested' ]; 205 | } else if ( reusableItems.length ) { 206 | openPanels = [ 'reusable' ]; 207 | } else if ( filteredItems.length ) { 208 | const firstCategory = find( getCategories(), ( { slug } ) => itemsPerCategory[ slug ] && itemsPerCategory[ slug ].length ); 209 | openPanels = [ firstCategory.slug ]; 210 | } 211 | } 212 | 213 | this.setState( { 214 | hoveredItem: null, 215 | childItems, 216 | filterValue, 217 | suggestedItems, 218 | reusableItems, 219 | itemsPerCategory, 220 | openPanels, 221 | } ); 222 | 223 | const resultCount = Object.keys( itemsPerCategory ).reduce( ( accumulator, currentCategorySlug ) => { 224 | return accumulator + itemsPerCategory[ currentCategorySlug ].length; 225 | }, 0 ); 226 | 227 | const resultsFoundMessage = sprintf( 228 | _n( '%d result found.', '%d results found.', resultCount ), 229 | resultCount 230 | ); 231 | 232 | debouncedSpeak( resultsFoundMessage, 'assertive' ); 233 | } 234 | 235 | onKeyDown( event ) { 236 | if ( includes( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ], event.keyCode ) ) { 237 | // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. 238 | event.stopPropagation(); 239 | } 240 | } 241 | 242 | render() { 243 | const { instanceId, onSelect, rootClientId } = this.props; 244 | const { childItems, filterValue, hoveredItem, suggestedItems, reusableItems, itemsPerCategory, openPanels } = this.state; 245 | const isPanelOpen = ( panel ) => openPanels.indexOf( panel ) !== -1; 246 | const isSearching = !! filterValue; 247 | 248 | // Disable reason (no-autofocus): The inserter menu is a modal display, not one which 249 | // is always visible, and one which already incurs this behavior of autoFocus via 250 | // Popover's focusOnMount. 251 | // Disable reason (no-static-element-interactions): Navigational key-presses within 252 | // the menu are prevented from triggering WritingFlow and ObserveTyping interactions. 253 | /* eslint-disable jsx-a11y/no-autofocus, jsx-a11y/no-static-element-interactions */ 254 | return ( 255 |
260 | 263 | 271 | 272 |
279 | 280 | 286 | 287 | { !! suggestedItems.length && 288 | 294 | 295 | 296 | } 297 | 298 | 299 | 300 | { map( getCategories(), ( category ) => { 301 | const categoryItems = itemsPerCategory[ category.slug ]; 302 | if ( ! categoryItems || ! categoryItems.length ) { 303 | return null; 304 | } 305 | return ( 306 | 314 | 315 | 316 | ); 317 | } ) } 318 | 319 | { !! reusableItems.length && ( 320 | 328 | 329 | 333 | { __( 'Manage All Reusable Blocks' ) } 334 | 335 | 336 | ) } 337 | { isEmpty( suggestedItems ) && isEmpty( reusableItems ) && isEmpty( itemsPerCategory ) && ( 338 |

{ __( 'No blocks found.' ) }

339 | ) } 340 |
341 | 342 | { hoveredItem && isReusableBlock( hoveredItem ) && 343 | 344 | } 345 |
346 | ); 347 | /* eslint-enable jsx-a11y/no-autofocus, jsx-a11y/no-noninteractive-element-interactions */ 348 | } 349 | } 350 | 351 | export default compose( 352 | withSelect( ( select, { rootClientId } ) => { 353 | const { 354 | getInserterItems, 355 | getBlockName, 356 | } = select( 'core/editor' ); 357 | const { 358 | getChildBlockNames, 359 | } = select( 'core/blocks' ); 360 | 361 | const rootBlockName = getBlockName( rootClientId ); 362 | 363 | return { 364 | rootChildBlocks: getChildBlockNames( rootBlockName ), 365 | items: getInserterItems( rootClientId ), 366 | rootClientId, 367 | }; 368 | } ), 369 | withDispatch( ( dispatch, ownProps, { select } ) => { 370 | const { 371 | __experimentalFetchReusableBlocks: fetchReusableBlocks, 372 | showInsertionPoint, 373 | hideInsertionPoint, 374 | } = dispatch( 'core/editor' ); 375 | 376 | return { 377 | fetchReusableBlocks, 378 | showInsertionPoint, 379 | hideInsertionPoint, 380 | onSelect( item ) { 381 | const { 382 | replaceBlocks, 383 | insertBlock, 384 | } = dispatch( 'core/editor' ); 385 | const { getSelectedBlock } = select( 'core/editor' ); 386 | const { index, rootClientId } = ownProps; 387 | const { name, initialAttributes } = item; 388 | 389 | const selectedBlock = getSelectedBlock(); 390 | const insertedBlock = createBlock( name, initialAttributes ); 391 | if ( selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { 392 | replaceBlocks( selectedBlock.clientId, insertedBlock ); 393 | } else { 394 | insertBlock( insertedBlock, index, rootClientId ); 395 | } 396 | 397 | ownProps.onSelect(); 398 | }, 399 | }; 400 | } ), 401 | withSpokenMessages, 402 | withInstanceId, 403 | withSafeTimeout 404 | )( InserterMenu ); 405 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/editor/build-module/components/post-preview-button/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { get } from 'lodash'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | import { Component, renderToString } from '@wordpress/element'; 10 | import { Button /* , Path, SVG */ } from '@wordpress/components'; 11 | import { __, _x } from '@wordpress/i18n'; 12 | import { withSelect, withDispatch } from '@wordpress/data'; 13 | import { DotTip } from '@wordpress/nux'; 14 | import { ifCondition, compose } from '@wordpress/compose'; 15 | import { applyFilters } from '@wordpress/hooks'; 16 | 17 | function writeInterstitialMessage( targetDocument ) { 18 | let markup = renderToString( 19 |
20 | { /* GUTENBERG JS 21 | 22 | 23 | 24 | 25 | */ } 26 |

{ __( 'Generating preview…' ) }

27 |
28 | ); 29 | 30 | markup += ` 31 | 83 | `; 84 | 85 | /** 86 | * Filters the interstitial message shown when generating previews. 87 | * 88 | * @param {String} markup The preview interstitial markup. 89 | */ 90 | markup = applyFilters( 'editor.PostPreview.interstitialMarkup', markup ); 91 | 92 | targetDocument.write( markup ); 93 | targetDocument.title = __( 'Generating preview…' ); 94 | targetDocument.close(); 95 | } 96 | 97 | export class PostPreviewButton extends Component { 98 | constructor() { 99 | super( ...arguments ); 100 | 101 | this.openPreviewWindow = this.openPreviewWindow.bind( this ); 102 | } 103 | 104 | componentDidUpdate( prevProps ) { 105 | const { previewLink } = this.props; 106 | 107 | // This relies on the window being responsible to unset itself when 108 | // navigation occurs or a new preview window is opened, to avoid 109 | // unintentional forceful redirects. 110 | if ( previewLink && ! prevProps.previewLink ) { 111 | this.setPreviewWindowLink( previewLink ); 112 | } 113 | } 114 | 115 | /** 116 | * Sets the preview window's location to the given URL, if a preview window 117 | * exists and is not closed. 118 | * 119 | * @param {string} url URL to assign as preview window location. 120 | */ 121 | setPreviewWindowLink( url ) { 122 | const { previewWindow } = this; 123 | 124 | if ( previewWindow && ! previewWindow.closed ) { 125 | previewWindow.location = url; 126 | } 127 | } 128 | 129 | getWindowTarget() { 130 | const { postId } = this.props; 131 | return `wp-preview-${ postId }`; 132 | } 133 | 134 | openPreviewWindow( event ) { 135 | // Our Preview button has its 'href' and 'target' set correctly for a11y 136 | // purposes. Unfortunately, though, we can't rely on the default 'click' 137 | // handler since sometimes it incorrectly opens a new tab instead of reusing 138 | // the existing one. 139 | // https://github.com/WordPress/gutenberg/pull/8330 140 | event.preventDefault(); 141 | 142 | // Open up a Preview tab if needed. This is where we'll show the preview. 143 | if ( ! this.previewWindow || this.previewWindow.closed ) { 144 | this.previewWindow = window.open( '', this.getWindowTarget() ); 145 | } 146 | 147 | // Focus the Preview tab. This might not do anything, depending on the browser's 148 | // and user's preferences. 149 | // https://html.spec.whatwg.org/multipage/interaction.html#dom-window-focus 150 | this.previewWindow.focus(); 151 | 152 | // If we don't need to autosave the post before previewing, then we simply 153 | // load the Preview URL in the Preview tab. 154 | if ( ! this.props.isAutosaveable ) { 155 | this.setPreviewWindowLink( event.target.href ); 156 | return; 157 | } 158 | 159 | // Request an autosave. This happens asynchronously and causes the component 160 | // to update when finished. 161 | if ( this.props.isDraft ) { 162 | this.props.savePost( { isPreview: true } ); 163 | } else { 164 | this.props.autosave( { isPreview: true } ); 165 | } 166 | 167 | // Display a 'Generating preview' message in the Preview tab while we wait for the 168 | // autosave to finish. 169 | writeInterstitialMessage( this.previewWindow.document ); 170 | } 171 | 172 | render() { 173 | const { previewLink, currentPostLink, isSaveable } = this.props; 174 | 175 | // Link to the `?preview=true` URL if we have it, since this lets us see 176 | // changes that were autosaved since the post was last published. Otherwise, 177 | // just link to the post's URL. 178 | const href = previewLink || currentPostLink; 179 | 180 | return ( 181 | 200 | ); 201 | } 202 | } 203 | 204 | export default compose( [ 205 | withSelect( ( select, { forcePreviewLink, forceIsAutosaveable } ) => { 206 | const { 207 | getCurrentPostId, 208 | getCurrentPostAttribute, 209 | getEditedPostAttribute, 210 | isEditedPostSaveable, 211 | isEditedPostAutosaveable, 212 | getEditedPostPreviewLink, 213 | } = select( 'core/editor' ); 214 | const { 215 | getPostType, 216 | } = select( 'core' ); 217 | 218 | const previewLink = getEditedPostPreviewLink(); 219 | const postType = getPostType( getEditedPostAttribute( 'type' ) ); 220 | return { 221 | postId: getCurrentPostId(), 222 | currentPostLink: getCurrentPostAttribute( 'link' ), 223 | previewLink: forcePreviewLink !== undefined ? forcePreviewLink : previewLink, 224 | isSaveable: isEditedPostSaveable(), 225 | isAutosaveable: forceIsAutosaveable || isEditedPostAutosaveable(), 226 | isViewable: get( postType, [ 'viewable' ], false ), 227 | isDraft: [ 'draft', 'auto-draft' ].indexOf( getEditedPostAttribute( 'status' ) ) !== -1, 228 | }; 229 | } ), 230 | withDispatch( ( dispatch ) => ( { 231 | autosave: dispatch( 'core/editor' ).autosave, 232 | savePost: dispatch( 'core/editor' ).savePost, 233 | } ) ), 234 | ifCondition( ( { isViewable } ) => isViewable ), 235 | ] )( PostPreviewButton ); 236 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/editor/build-module/components/post-publish-button/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { noop, get } from 'lodash'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | import { Button } from '@wordpress/components'; 10 | import { Component, createRef } from '@wordpress/element'; 11 | import { withSelect, withDispatch } from '@wordpress/data'; 12 | import { compose, ifCondition } from '@wordpress/compose'; 13 | import { __ } from '@wordpress/i18n'; 14 | import { DotTip } from '@wordpress/nux'; 15 | 16 | /** 17 | * Internal dependencies 18 | */ 19 | import PublishButtonLabel from './label'; 20 | export class PostPublishButton extends Component { 21 | constructor( props ) { 22 | super( props ); 23 | this.buttonNode = createRef(); 24 | } 25 | componentDidMount() { 26 | if ( this.props.focusOnMount ) { 27 | this.buttonNode.current.focus(); 28 | } 29 | } 30 | 31 | render() { 32 | const { 33 | forceIsDirty, 34 | forceIsSaving, 35 | hasPublishAction, 36 | isBeingScheduled, 37 | isOpen, 38 | isPostSavingLocked, 39 | isPublishable, 40 | isPublished, 41 | isSaveable, 42 | isSaving, 43 | isToggle, 44 | onSave, 45 | onStatusChange, 46 | onSubmit = noop, 47 | onToggle, 48 | visibility, 49 | } = this.props; 50 | const isButtonDisabled = 51 | isSaving || 52 | forceIsSaving || 53 | ! isSaveable || 54 | isPostSavingLocked || 55 | ( ! isPublishable && ! forceIsDirty ); 56 | 57 | const isToggleDisabled = 58 | isPublished || 59 | isSaving || 60 | forceIsSaving || 61 | ! isSaveable || 62 | ( ! isPublishable && ! forceIsDirty ); 63 | 64 | let publishStatus; 65 | if ( ! hasPublishAction ) { 66 | publishStatus = 'pending'; 67 | } else if ( isBeingScheduled ) { 68 | publishStatus = 'future'; 69 | } else if ( visibility === 'private' ) { 70 | publishStatus = 'private'; 71 | } else { 72 | publishStatus = 'publish'; 73 | } 74 | 75 | const onClickButton = () => { 76 | if ( isButtonDisabled ) { 77 | return; 78 | } 79 | onSubmit(); 80 | onStatusChange( publishStatus ); 81 | onSave(); 82 | }; 83 | 84 | const onClickToggle = () => { 85 | if ( isToggleDisabled ) { 86 | return; 87 | } 88 | onToggle(); 89 | }; 90 | 91 | const buttonProps = { 92 | 'aria-disabled': isButtonDisabled, 93 | className: 'editor-post-publish-button', 94 | isBusy: isSaving && isPublished, 95 | isLarge: true, 96 | isPrimary: true, 97 | onClick: onClickButton, 98 | }; 99 | 100 | const toggleProps = { 101 | 'aria-disabled': isToggleDisabled, 102 | 'aria-expanded': isOpen, 103 | className: 'editor-post-publish-panel__toggle', 104 | isBusy: isSaving && isPublished, 105 | isPrimary: true, 106 | onClick: onClickToggle, 107 | }; 108 | 109 | const toggleChildren = isBeingScheduled ? __( 'Schedule…' ) : __( 'Publish…' ); 110 | const buttonChildren = ; 111 | 112 | const componentProps = isToggle ? toggleProps : buttonProps; 113 | const componentChildren = isToggle ? toggleChildren : buttonChildren; 114 | return ( 115 | 124 | ); 125 | } 126 | } 127 | 128 | export default compose( [ 129 | withSelect( ( select ) => { 130 | const { 131 | isSavingPost, 132 | isEditedPostBeingScheduled, 133 | getEditedPostVisibility, 134 | isCurrentPostPublished, 135 | isEditedPostSaveable, 136 | isEditedPostPublishable, 137 | isPostSavingLocked, 138 | getCurrentPost, 139 | getCurrentPostType, 140 | 141 | // GUTENBERG JS 142 | getEditorSettings, 143 | } = select( 'core/editor' ); 144 | 145 | // GUTENBERG JS 146 | const { canPublish } = getEditorSettings(); 147 | 148 | return { 149 | isSaving: isSavingPost(), 150 | isBeingScheduled: isEditedPostBeingScheduled(), 151 | visibility: getEditedPostVisibility(), 152 | isSaveable: isEditedPostSaveable(), 153 | isPostSavingLocked: isPostSavingLocked(), 154 | isPublishable: isEditedPostPublishable(), 155 | isPublished: isCurrentPostPublished(), 156 | hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), 157 | postType: getCurrentPostType(), 158 | 159 | // GUTENBERG JS 160 | canPublish, 161 | }; 162 | } ), 163 | withDispatch( ( dispatch ) => { 164 | const { editPost, savePost } = dispatch( 'core/editor' ); 165 | return { 166 | onStatusChange: ( status ) => editPost( { status } ), 167 | onSave: savePost, 168 | }; 169 | } ), 170 | 171 | // GUTENBERG JS 172 | // added the ifCondition to enable/disable 173 | // the publish button according 'canPublish' setting 174 | ifCondition( ( { canPublish } ) => canPublish ), 175 | ] )( PostPublishButton ); 176 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/editor/build-module/components/post-publish-panel/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { get, omit } from 'lodash'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | import { __ } from '@wordpress/i18n'; 10 | import { Component } from '@wordpress/element'; 11 | import { 12 | IconButton, 13 | Spinner, 14 | CheckboxControl, 15 | withFocusReturn, 16 | withConstrainedTabbing, 17 | } from '@wordpress/components'; 18 | import { withSelect, withDispatch } from '@wordpress/data'; 19 | import { compose, ifCondition } from '@wordpress/compose'; 20 | 21 | /** 22 | * Internal Dependencies 23 | */ 24 | import PostPublishButton from '../post-publish-button'; 25 | import PostPublishPanelPrepublish from './prepublish'; 26 | import PostPublishPanelPostpublish from './postpublish'; 27 | 28 | export class PostPublishPanel extends Component { 29 | constructor() { 30 | super( ...arguments ); 31 | this.onSubmit = this.onSubmit.bind( this ); 32 | } 33 | 34 | componentDidUpdate( prevProps ) { 35 | // Automatically collapse the publish sidebar when a post 36 | // is published and the user makes an edit. 37 | if ( prevProps.isPublished && ! this.props.isSaving && this.props.isDirty ) { 38 | this.props.onClose(); 39 | } 40 | } 41 | 42 | onSubmit() { 43 | const { onClose, hasPublishAction, isPostTypeViewable } = this.props; 44 | if ( ! hasPublishAction || ! isPostTypeViewable ) { 45 | onClose(); 46 | } 47 | } 48 | 49 | render() { 50 | const { 51 | forceIsDirty, 52 | forceIsSaving, 53 | isBeingScheduled, 54 | isPublished, 55 | isPublishSidebarEnabled, 56 | isScheduled, 57 | isSaving, 58 | onClose, 59 | onTogglePublishSidebar, 60 | PostPublishExtension, 61 | PrePublishExtension, 62 | ...additionalProps 63 | } = this.props; 64 | const propsForPanel = omit( additionalProps, [ 'hasPublishAction', 'isDirty', 'isPostTypeViewable' ] ); 65 | const isPublishedOrScheduled = isPublished || ( isScheduled && isBeingScheduled ); 66 | const isPrePublish = ! isPublishedOrScheduled && ! isSaving; 67 | const isPostPublish = isPublishedOrScheduled && ! isSaving; 68 | return ( 69 |
70 |
71 | { isPostPublish ? ( 72 |
73 | { isScheduled ? __( 'Scheduled' ) : __( 'Published' ) } 74 |
75 | ) : ( 76 |
77 | 78 | 79 |
80 | ) } 81 | 87 |
88 |
89 | { isPrePublish && ( 90 | 91 | { PrePublishExtension && } 92 | 93 | ) } 94 | { isPostPublish && ( 95 | 96 | { PostPublishExtension && } 97 | 98 | ) } 99 | { isSaving && ( ) } 100 |
101 |
102 | 107 |
108 |
109 | ); 110 | } 111 | } 112 | 113 | export default compose( [ 114 | withSelect( ( select ) => { 115 | const { getPostType } = select( 'core' ); 116 | const { 117 | getCurrentPost, 118 | getEditedPostAttribute, 119 | isCurrentPostPublished, 120 | isCurrentPostScheduled, 121 | isEditedPostBeingScheduled, 122 | isEditedPostDirty, 123 | isSavingPost, 124 | 125 | // GUTENBERG JS 126 | getEditorSettings, 127 | } = select( 'core/editor' ); 128 | const { isPublishSidebarEnabled } = select( 'core/editor' ); 129 | const postType = getPostType( getEditedPostAttribute( 'type' ) ); 130 | 131 | // GUTENBERG JS 132 | const { canPublish } = getEditorSettings(); 133 | 134 | return { 135 | hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), 136 | isPostTypeViewable: get( postType, [ 'viewable' ], false ), 137 | isBeingScheduled: isEditedPostBeingScheduled(), 138 | isDirty: isEditedPostDirty(), 139 | isPublished: isCurrentPostPublished(), 140 | isPublishSidebarEnabled: isPublishSidebarEnabled(), 141 | isSaving: isSavingPost(), 142 | isScheduled: isCurrentPostScheduled(), 143 | 144 | // GUTENBERG JS 145 | canPublish, 146 | }; 147 | } ), 148 | withDispatch( ( dispatch, { isPublishSidebarEnabled } ) => { 149 | const { disablePublishSidebar, enablePublishSidebar } = dispatch( 'core/editor' ); 150 | return { 151 | onTogglePublishSidebar: ( ) => { 152 | if ( isPublishSidebarEnabled ) { 153 | disablePublishSidebar(); 154 | } else { 155 | enablePublishSidebar(); 156 | } 157 | }, 158 | }; 159 | } ), 160 | withFocusReturn, 161 | withConstrainedTabbing, 162 | 163 | // GUTENBERG JS 164 | // added the ifCondition to enable/disable 165 | // the publish button according 'canPublish' setting 166 | ifCondition( ( { canPublish } ) => canPublish ), 167 | ] )( PostPublishPanel ); 168 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/editor/build-module/components/post-saved-state/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | import { get } from 'lodash'; 6 | 7 | /** 8 | * WordPress dependencies 9 | */ 10 | import { __ } from '@wordpress/i18n'; 11 | import { Dashicon, Button, IconButton } from '@wordpress/components'; 12 | import { Component } from '@wordpress/element'; 13 | import { withSelect, withDispatch } from '@wordpress/data'; 14 | import { displayShortcut } from '@wordpress/keycodes'; 15 | import { withSafeTimeout, compose, ifCondition } from '@wordpress/compose'; 16 | import { withViewportMatch } from '@wordpress/viewport'; 17 | 18 | /** 19 | * Internal dependencies 20 | */ 21 | import PostSwitchToDraftButton from '../post-switch-to-draft-button'; 22 | 23 | /** 24 | * Component showing whether the post is saved or not and displaying save links. 25 | * 26 | * @param {Object} Props Component Props. 27 | */ 28 | export class PostSavedState extends Component { 29 | constructor() { 30 | super( ...arguments ); 31 | this.state = { 32 | forceSavedMessage: false, 33 | }; 34 | } 35 | 36 | componentDidUpdate( prevProps ) { 37 | if ( prevProps.isSaving && ! this.props.isSaving ) { 38 | this.setState( { forceSavedMessage: true } ); 39 | this.props.setTimeout( () => { 40 | this.setState( { forceSavedMessage: false } ); 41 | }, 1000 ); 42 | } 43 | } 44 | 45 | render() { 46 | const { 47 | post, 48 | isNew, 49 | isScheduled, 50 | isPublished, 51 | isDirty, 52 | isSaving, 53 | isSaveable, 54 | onSave, 55 | isAutosaving, 56 | isPending, 57 | isLargeViewport, 58 | } = this.props; 59 | const { forceSavedMessage } = this.state; 60 | if ( isSaving ) { 61 | // TODO: Classes generation should be common across all return 62 | // paths of this function, including proper naming convention for 63 | // the "Save Draft" button. 64 | const classes = classnames( 'editor-post-saved-state', 'is-saving', { 65 | 'is-autosaving': isAutosaving, 66 | } ); 67 | 68 | return ( 69 | 70 | 71 | { isAutosaving ? __( 'Autosaving' ) : __( 'Saving' ) } 72 | 73 | ); 74 | } 75 | 76 | if ( isPublished || isScheduled ) { 77 | return ; 78 | } 79 | 80 | if ( ! isSaveable ) { 81 | return null; 82 | } 83 | 84 | if ( forceSavedMessage || ( ! isNew && ! isDirty ) ) { 85 | return ( 86 | 87 | 88 | { __( 'Saved' ) } 89 | 90 | ); 91 | } 92 | 93 | // Once the post has been submitted for review this button 94 | // is not needed for the contributor role. 95 | const hasPublishAction = get( post, [ '_links', 'wp:action-publish' ], false ); 96 | if ( ! hasPublishAction && isPending ) { 97 | return null; 98 | } 99 | 100 | const label = isPending ? __( 'Save as Pending' ) : __( 'Save Draft' ); 101 | if ( ! isLargeViewport ) { 102 | return ( 103 | 110 | ); 111 | } 112 | 113 | return ( 114 | 122 | ); 123 | } 124 | } 125 | 126 | export default compose( [ 127 | withSelect( ( select, { forceIsDirty, forceIsSaving } ) => { 128 | const { 129 | isEditedPostNew, 130 | isCurrentPostPublished, 131 | isCurrentPostScheduled, 132 | isEditedPostDirty, 133 | isSavingPost, 134 | isEditedPostSaveable, 135 | getCurrentPost, 136 | isAutosavingPost, 137 | getEditedPostAttribute, 138 | 139 | // GUTENBERG JS 140 | getEditorSettings, 141 | } = select( 'core/editor' ); 142 | 143 | // getting canSave setting 144 | const { canSave } = getEditorSettings(); 145 | 146 | return { 147 | post: getCurrentPost(), 148 | isNew: isEditedPostNew(), 149 | isPublished: isCurrentPostPublished(), 150 | isScheduled: isCurrentPostScheduled(), 151 | isDirty: forceIsDirty || isEditedPostDirty(), 152 | isSaving: forceIsSaving || isSavingPost(), 153 | isSaveable: isEditedPostSaveable(), 154 | isAutosaving: isAutosavingPost(), 155 | isPending: 'pending' === getEditedPostAttribute( 'status' ), 156 | 157 | // GUTENBERG JS 158 | canSave, 159 | }; 160 | } ), 161 | withDispatch( ( dispatch ) => ( { 162 | onSave: dispatch( 'core/editor' ).savePost, 163 | } ) ), 164 | withSafeTimeout, 165 | withViewportMatch( { isLargeViewport: 'medium' } ), 166 | 167 | // GUTENBERG JS 168 | // added the ifCondition to enable/disable 169 | // the save button according 'canSave' setting 170 | ifCondition( ( { canSave } ) => canSave ), 171 | ] )( PostSavedState ); 172 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/editor/build-module/store/defaults.js: -------------------------------------------------------------------------------- 1 | import * as others from '@wordpress/editor/build-module/store/defaults?source=node_modules'; 2 | 3 | // Properties to handle with save and publish features 4 | others.EDITOR_SETTINGS_DEFAULTS.canPublish = true; 5 | others.EDITOR_SETTINGS_DEFAULTS.canSave = true; 6 | others.EDITOR_SETTINGS_DEFAULTS.canAutosave = true; 7 | 8 | // Property to show/hide media library option 9 | others.EDITOR_SETTINGS_DEFAULTS.mediaLibrary = true; 10 | 11 | export * from '@wordpress/editor/build-module/store/defaults?source=node_modules'; 12 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/editor/build-module/store/selectors.js: -------------------------------------------------------------------------------- 1 | import * as others from '@wordpress/editor/build-module/store/selectors?source=node_modules'; 2 | 3 | // no utility level => no Most Used blocks section 4 | others.INSERTER_UTILITY_HIGH = others.INSERTER_UTILITY_NONE; 5 | others.INSERTER_UTILITY_MEDIUM = others.INSERTER_UTILITY_NONE; 6 | others.INSERTER_UTILITY_LOW = others.INSERTER_UTILITY_NONE; 7 | 8 | export * from '@wordpress/editor/build-module/store/selectors?source=node_modules'; 9 | -------------------------------------------------------------------------------- /src/js/gutenberg-overrides/@wordpress/notices/build/store/actions.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | GUTENBERG JS - 2019-01-14 4 | This module needs to be recompiled or otherwise the store will not be avaiable. 5 | It's not clear why. More discovery is needed. 6 | However, this line replaces the previous code duplication. 7 | */ 8 | export * from '@wordpress/notices/src/store/actions?source=node_modules'; 9 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import './init'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | import * as hooks from '@wordpress/hooks'; 10 | import * as i18n from '@wordpress/i18n'; 11 | // wp.i18n.setLocaleData 12 | 13 | import * as url from '@wordpress/url'; 14 | 15 | import apiFetch from '@wordpress/api-fetch'; 16 | import './scripts/api-fetch'; 17 | 18 | import * as autop from '@wordpress/autop'; 19 | import * as blob from '@wordpress/blob'; 20 | import * as blockSerializationDefaultParser from '@wordpress/block-serialization-default-parser'; 21 | 22 | import * as escapeHtml from '@wordpress/escape-html'; 23 | import * as element from '@wordpress/element'; 24 | import * as isShallowEqual from '@wordpress/is-shallow-equal'; 25 | import * as compose from '@wordpress/compose'; 26 | import * as reduxRoutine from '@wordpress/redux-routine'; 27 | import * as data from '@wordpress/data'; 28 | import './scripts/data'; 29 | 30 | import * as dom from '@wordpress/dom'; 31 | import * as htmlEntities from '@wordpress/html-entities'; 32 | import * as shortcode from '@wordpress/shortcode'; 33 | 34 | import * as blocks from '@wordpress/blocks'; 35 | // wp.blocks.setCategories 36 | // wp.blocks.unstable__bootstrapServerSideBlockDefinitions 37 | 38 | import * as keycodes from '@wordpress/keycodes'; 39 | import * as richText from '@wordpress/rich-text'; 40 | // wp.i18n.setLocaleData 41 | 42 | import * as components from '@wordpress/components'; 43 | import * as coreData from '@wordpress/core-data'; 44 | import * as date from '@wordpress/date'; 45 | // wp.date.setSettings 46 | 47 | import deprecated from '@wordpress/deprecated'; 48 | import * as notices from '@wordpress/notices'; 49 | // wp.i18n.setLocaleData 50 | 51 | import * as nux from '@wordpress/nux'; 52 | import * as tokenList from '@wordpress/token-list'; 53 | import * as viewport from '@wordpress/viewport'; 54 | import * as wordcount from '@wordpress/wordcount'; 55 | // _wpMetaBoxUrl 56 | // wp.i18n.setLocaleData 57 | 58 | import { editor, oldEditor } from './scripts/editor.js'; 59 | // wp.i18n.setLocaleData 60 | // window.wpEditorL10n 61 | 62 | import * as blockLibrary from '@wordpress/block-library'; 63 | import domReady from '@wordpress/dom-ready'; 64 | import * as plugins from '@wordpress/plugins'; 65 | // wp.i18n.setLocaleData 66 | 67 | import * as editPost from '@wordpress/edit-post'; 68 | // window._wpLoadBlockEditor 69 | 70 | import * as formatLibrary from '@wordpress/format-library'; 71 | 72 | // Other 73 | import * as a11y from '@wordpress/a11y'; 74 | 75 | // Style 76 | import '../scss/block-library.scss'; 77 | import '../scss/style.scss'; 78 | 79 | // Set global wp 80 | window.wp = { 81 | a11y, 82 | apiFetch, 83 | autop, 84 | blob, 85 | blockLibrary, 86 | blockSerializationDefaultParser, 87 | blocks, 88 | components, 89 | compose, 90 | coreData, 91 | data, 92 | date, 93 | deprecated, 94 | dom, 95 | domReady, 96 | editPost, 97 | editor, 98 | element, 99 | escapeHtml, 100 | formatLibrary, 101 | hooks, 102 | htmlEntities, 103 | i18n, 104 | isShallowEqual, 105 | keycodes, 106 | notices, 107 | nux, 108 | oldEditor, 109 | plugins, 110 | reduxRoutine, 111 | richText, 112 | shortcode, 113 | tokenList, 114 | url, 115 | viewport, 116 | wordcount, 117 | }; 118 | 119 | export { 120 | a11y, 121 | apiFetch, 122 | autop, 123 | blob, 124 | blockLibrary, 125 | blockSerializationDefaultParser, 126 | blocks, 127 | components, 128 | compose, 129 | coreData, 130 | data, 131 | date, 132 | deprecated, 133 | dom, 134 | domReady, 135 | editPost, 136 | editor, 137 | element, 138 | escapeHtml, 139 | formatLibrary, 140 | hooks, 141 | htmlEntities, 142 | i18n, 143 | isShallowEqual, 144 | keycodes, 145 | notices, 146 | nux, 147 | oldEditor, 148 | plugins, 149 | reduxRoutine, 150 | richText, 151 | shortcode, 152 | tokenList, 153 | url, 154 | viewport, 155 | wordcount, 156 | }; 157 | -------------------------------------------------------------------------------- /src/js/init/api-fetch.js: -------------------------------------------------------------------------------- 1 | import apiFetch from '@wordpress/api-fetch/build-module'; 2 | 3 | if (window.wp.apiFetch) { 4 | const props = Object.keys(apiFetch); 5 | 6 | props.forEach(prop => { 7 | window.wp.apiFetch[prop] = window.wp.apiFetch[prop] || apiFetch[prop]; 8 | }); 9 | } 10 | else { 11 | window.wp.apiFetch = apiFetch; 12 | } 13 | -------------------------------------------------------------------------------- /src/js/init/index.js: -------------------------------------------------------------------------------- 1 | export * from './wp.js'; 2 | export * from './url.js'; 3 | export * from './api-fetch.js'; 4 | -------------------------------------------------------------------------------- /src/js/init/url.js: -------------------------------------------------------------------------------- 1 | import * as url from '@wordpress/url/build-module'; 2 | 3 | if (window.wp.url) { 4 | const props = Object.keys(url); 5 | 6 | props.forEach(prop => { 7 | window.wp.url[prop] = window.wp.url[prop] || url[prop]; 8 | }); 9 | } 10 | else { 11 | window.wp.url = url; 12 | } 13 | -------------------------------------------------------------------------------- /src/js/init/wp.js: -------------------------------------------------------------------------------- 1 | window.wp = window.wp || {}; 2 | 3 | // User settings 4 | window.userSettings = window.userSettings || { 5 | secure: '', 6 | time: 1234567, 7 | uid: 1, 8 | }; 9 | 10 | // API settings 11 | window.wpApiSettings = window.wpApiSettings || {}; 12 | window.wpApiSettingsroot = window.wpApiSettings.root || window.location.origin; 13 | window.wpApiSettingsnonce = window.wpApiSettings.nonce || '123456789'; 14 | window.wpApiSettingsversionString = window.wpApiSettings.versionString || 'wp/v2/'; 15 | 16 | // postboxes 17 | window.postboxes = window.postboxes || { 18 | add_postbox_toggles: (page, args) => { 19 | console.log('page', page); 20 | console.log('args', args); 21 | }, 22 | }; 23 | 24 | // editorL10n 25 | window.wpEditorL10n = window.wpEditorL10n || { 26 | tinymce: { 27 | baseUrl: 'node_modules/tinymce', 28 | settings: { 29 | external_plugins: [], 30 | plugins: 'charmap,colorpicker,hr,lists,media,paste,tabfocus,textcolor,fullscreen', // ,wordpress,wpautoresize,wpeditimage,wpemoji,wpgallery,wplink,wpdialogs,wptextpattern,wpview', 31 | toolbar1: 'formatselect,bold,italic,bullist,numlist,blockquote,alignleft,aligncenter,alignright,link,unlink,wp_more,spellchecker,kitchensink', 32 | toolbar2: 'strikethrough,hr,forecolor,pastetext,removeformat,charmap,outdent,indent,undo,redo,wp_help', 33 | toolbar3: '', 34 | toolbar4: '', 35 | }, 36 | suffix: '.min', 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/js/scripts/api-fetch.js: -------------------------------------------------------------------------------- 1 | import apiFetch from '@wordpress/api-fetch'; 2 | 3 | // Middleware 4 | apiFetch.use(apiFetch.createNonceMiddleware(window.wpApiSettings.nonce)); 5 | apiFetch.use(apiFetch.createRootURLMiddleware(window.wpApiSettings.root)); 6 | -------------------------------------------------------------------------------- /src/js/scripts/data.js: -------------------------------------------------------------------------------- 1 | import * as data from '@wordpress/data'; 2 | 3 | const uid = window.userSettings ? window.userSettings.uid || 1 : 1; 4 | const storageKey = `WP_DATA_USER_${uid}`; 5 | 6 | data.use(data.plugins.persistence, { storageKey }); 7 | data.use(data.plugins.controls); 8 | -------------------------------------------------------------------------------- /src/js/scripts/editor.js: -------------------------------------------------------------------------------- 1 | import * as oEditor from '@wordpress/editor'; 2 | import tinymce from 'tinymce'; 3 | 4 | window.tinymce = window.tinymce || tinymce; 5 | 6 | const oldEditorFunctions = { 7 | initialize: (id, settings = { tinymce: true }) => { 8 | const defaults = window.wp.editor.getDefaultSettings(); 9 | const init = jQuery.extend({}, defaults.tinymce, settings.tinymce); 10 | 11 | init.selector = '#' + id; 12 | 13 | window.tinymce.init(init); 14 | 15 | if (! window.wpActiveEditor) { 16 | window.wpActiveEditor = id; 17 | } 18 | }, 19 | 20 | autop: () => {}, 21 | 22 | getContent: id => { 23 | const editor = window.tinymce.get(id); 24 | 25 | if (editor && ! editor.isHidden()) { 26 | editor.save(); 27 | } 28 | 29 | return jQuery('#' + id).val(); 30 | }, 31 | 32 | remove: id => { 33 | const mceInstance = window.tinymce.get(id); 34 | 35 | if (mceInstance) { 36 | if (! mceInstance.isHidden()) { 37 | mceInstance.save(); 38 | } 39 | 40 | mceInstance.remove(); 41 | } 42 | }, 43 | 44 | removep: () => {}, 45 | 46 | getDefaultSettings: () => ({ 47 | tinymce: { 48 | indent: true, 49 | keep_styles: false, 50 | menubar: false, 51 | plugins: 'charmap,colorpicker,hr,lists,media,paste,tabfocus,textcolor,fullscreen', 52 | resize: 'vertical', 53 | skin: 'lightgray', 54 | theme: 'modern', 55 | toolbar1: 'bold,italic,bullist,numlist,link', 56 | }, 57 | quicktags: { 58 | buttons: 'strong,em,link,ul,ol,li,code', 59 | }, 60 | }), 61 | }; 62 | 63 | const editor = { 64 | ...oEditor, 65 | ...oldEditorFunctions, 66 | }; 67 | 68 | const oldEditor = editor; 69 | 70 | export { 71 | editor, 72 | oldEditor, 73 | }; 74 | -------------------------------------------------------------------------------- /src/scss/_media-library.scss: -------------------------------------------------------------------------------- 1 | .media-library__popover { 2 | > div { 3 | padding: 1em; 4 | } 5 | 6 | .media-library__popover__content { 7 | max-height: 465px; 8 | display: grid; 9 | grid-template-columns: repeat(5, auto); 10 | grid-gap: 1em; 11 | 12 | .media-library-thumbnail { 13 | width: 100px; 14 | height: 100px; 15 | border: 1px solid #e2e4e7; 16 | padding: 5px; 17 | background-position: center; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-color: hsla(240,5%,57%,.1); 21 | 22 | &:hover { 23 | cursor: pointer; 24 | box-shadow: inset 0 0 0 1px rgba(25,30,35,.2), 0 1px 3px rgba(25,30,35,.4); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/scss/block-library.scss: -------------------------------------------------------------------------------- 1 | @import '~@wordpress/block-library/build-style/style.css'; 2 | 3 | /* 4 | * TEMPORARY FIXES 5 | */ 6 | -------------------------------------------------------------------------------- /src/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import '~@wordpress/format-library/build-style/style.css'; 2 | @import '~@wordpress/components/build-style/style.css'; 3 | @import '~@wordpress/nux/build-style/style.css'; 4 | @import '~@wordpress/editor/build-style/style.css'; 5 | @import '~@wordpress/block-library/build-style/theme.css'; 6 | @import '~@wordpress/block-library/build-style/editor.css'; 7 | @import '~@wordpress/edit-post/build-style/style.css'; 8 | 9 | @import 'media-library'; 10 | 11 | @media (min-width: 600px) { 12 | body .gutenberg__editor { 13 | position: relative; 14 | } 15 | } 16 | 17 | @media (min-width: 782px) { 18 | .gutenberg__editor .edit-post-sidebar { 19 | top: 56px; 20 | } 21 | } 22 | 23 | .gutenberg__editor { 24 | position: relative; 25 | 26 | * { 27 | box-sizing: border-box; 28 | 29 | &:focus { 30 | outline-width: 0; 31 | } 32 | } 33 | 34 | input { 35 | margin: 1px; 36 | } 37 | 38 | input[type=checkbox] { 39 | border: 1px solid #b4b9be; 40 | background: #fff; 41 | color: #555; 42 | clear: none; 43 | cursor: pointer; 44 | display: inline-block; 45 | line-height: 0; 46 | height: 16px; 47 | margin: -4px 4px 0 0; 48 | outline: 0; 49 | padding: 0!important; 50 | text-align: center; 51 | vertical-align: middle; 52 | width: 16px; 53 | min-width: 16px; 54 | box-shadow: inset 0 1px 2px rgba(0,0,0,.1); 55 | transition: .05s border-color ease-in-out; 56 | } 57 | 58 | .edit-post-layout { 59 | padding-top: 0; 60 | 61 | @media (min-width: 960px) { 62 | padding-top: 0; 63 | } 64 | 65 | @media (min-width: 600px) { 66 | padding-top: 0; 67 | } 68 | 69 | .edit-post-header, 70 | .edit-post-sidebar, 71 | .components-popover { 72 | font-family: inherit; 73 | font-size: 13px; 74 | } 75 | 76 | .edit-post-header { 77 | @media (min-width: 782px) { 78 | position: sticky; 79 | left: 0; 80 | top: 0; 81 | } 82 | } 83 | 84 | .components-notice-list { 85 | @media (min-width: 782px) { 86 | left: 0; 87 | } 88 | 89 | .notice, 90 | div.error, 91 | div.updated { 92 | background: #fff; 93 | border-left: 4px solid #fff; 94 | } 95 | 96 | .notice-alt { 97 | box-shadow: none; 98 | } 99 | 100 | .notice-success.notice-alt { 101 | background-color: #ecf7ed; 102 | } 103 | 104 | .notice-success, 105 | div.updated { 106 | border-left-color: #46b450; 107 | } 108 | 109 | .notice-dismiss { 110 | position: absolute; 111 | top: 0; 112 | right: 1px; 113 | border: none; 114 | margin: 5px; 115 | padding: 9px; 116 | background: 0 0; 117 | color: #72777c; 118 | cursor: pointer; 119 | } 120 | } 121 | 122 | .edit-post-text-editor { 123 | @media (min-width: 960px) { 124 | padding-left: 20px; 125 | padding-right: 20px; 126 | } 127 | 128 | .edit-post-text-editor__body { 129 | padding-top: 0px; 130 | 131 | @media (min-width: 782px) { 132 | padding-top: 0px; 133 | } 134 | 135 | @media (min-width: 600px) { 136 | padding-top: 0px; 137 | } 138 | 139 | .editor-post-title { 140 | @media (min-width: 600px) { 141 | padding: 5px 0; 142 | } 143 | } 144 | } 145 | } 146 | 147 | .edit-post-sidebar { 148 | .edit-post-sidebar__panel-tabs { 149 | ul { 150 | list-style: none; 151 | margin: 0; 152 | padding: 0; 153 | } 154 | } 155 | } 156 | } 157 | 158 | .screen-reader-text, 159 | .screen-reader-text span, 160 | .ui-helper-hidden-accessible { 161 | border: 0; 162 | clip: rect(1px,1px,1px,1px); 163 | -webkit-clip-path: inset(50%); 164 | clip-path: inset(50%); 165 | height: 1px; 166 | margin: -1px; 167 | overflow: hidden; 168 | padding: 0; 169 | position: absolute; 170 | width: 1px; 171 | word-wrap: normal!important; 172 | } 173 | 174 | .edit-post-visual-editor { 175 | font-family: inherit; 176 | 177 | p { 178 | font-family: inherit; 179 | } 180 | 181 | .editor-post-title, 182 | .editor-post-title__block, 183 | .editor-block-list__block, 184 | .editor-default-block-appender { 185 | max-width: initial; 186 | margin-left: 35px; 187 | margin-right: 35px; 188 | } 189 | 190 | .editor-post-title>div { 191 | @media (min-width: 600px) { 192 | margin-left: 0; 193 | margin-right: 0; 194 | } 195 | } 196 | 197 | .editor-post-title { 198 | .editor-post-title__input { 199 | font-family: inherit; 200 | } 201 | } 202 | 203 | .editor-block-list__layout { 204 | .editor-block-list__block { 205 | &.is-selected, 206 | &.is-hovered { 207 | outline-width: 0; 208 | } 209 | } 210 | 211 | .editor-default-block-appender__content { 212 | font-family: inherit; 213 | } 214 | } 215 | } 216 | 217 | .components-color-palette__item:focus:after { 218 | top: -3px; 219 | left: -3px; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | const { resolve } = require('path'); 5 | 6 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 7 | const extractCSS = new ExtractTextPlugin('./css/style.css'); 8 | const extractBLCSS = new ExtractTextPlugin('./css/block-library/style.css'); 9 | 10 | // const PostCssWrapper = require('postcss-wrapper-loader'); 11 | // const StringReplacePlugin = require('string-replace-webpack-plugin'); 12 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 13 | 14 | /** 15 | * Given a string, returns a new string with dash separators converedd to 16 | * camel-case equivalent. This is not as aggressive as `_.camelCase` in 17 | * converting to uppercase, where Lodash will convert letters following 18 | * numbers. 19 | * 20 | * @param {string} string Input dash-delimited string. 21 | * 22 | * @return {string} Camel-cased string. 23 | */ 24 | function camelCaseDash (string) { 25 | return string.replace( 26 | /-([a-z])/g, 27 | (match, letter) => letter.toUpperCase() 28 | ); 29 | } 30 | 31 | const babelLoader = { 32 | loader: 'babel-loader', 33 | options: { 34 | presets: ['@babel/preset-react'], 35 | }, 36 | }; 37 | 38 | const externals = { 39 | react: 'React', 40 | 'react-dom': 'ReactDOM', 41 | moment: 'moment', 42 | jquery: 'jQuery', 43 | }; 44 | 45 | const alias = {}; 46 | 47 | [ 48 | 'api-fetch', 49 | 'url', 50 | ].forEach(name => { 51 | externals[ `@wordpress/${name}` ] = { 52 | this: [ 'wp', camelCaseDash(name) ], 53 | }; 54 | }); 55 | 56 | module.exports = { 57 | mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', 58 | devtool: 'source-map', 59 | entry: './src/js/index.js', 60 | output: { 61 | filename: 'js/gutenberg-js.js', 62 | path: resolve(__dirname, 'build'), 63 | libraryTarget: 'this', 64 | }, 65 | externals, 66 | resolve: { 67 | modules: [ 68 | __dirname, 69 | resolve(__dirname, 'node_modules'), 70 | ], 71 | alias, 72 | }, 73 | module: { 74 | rules: [ 75 | { 76 | test: /\.js$/, 77 | include: [ 78 | /src/, 79 | /node_modules\/@wordpress/, 80 | ], 81 | use: babelLoader, 82 | }, 83 | { 84 | test: /\.js$/, 85 | oneOf: [ 86 | { 87 | resourceQuery: /\?source=node_modules/, 88 | use: babelLoader, 89 | }, 90 | { 91 | loader: 'path-replace-loader', 92 | options: { 93 | path: resolve(__dirname, 'node_modules/@wordpress'), 94 | replacePath: resolve(__dirname, 'src/js/gutenberg-overrides/@wordpress'), 95 | }, 96 | }, 97 | ], 98 | }, 99 | /* { 100 | test: /editor\.s?css$/, 101 | include: [ 102 | /block-library/, 103 | ], 104 | use: mainCSSExtractTextPlugin.extract({ 105 | use: [ 106 | { 107 | // removing .gutenberg class in editor.scss files 108 | loader: StringReplacePlugin.replace({ 109 | replacements: [ { 110 | pattern: /.gutenberg /ig, 111 | // replacement: () => (''), 112 | replacement: () => ('.gutenberg__editor'), 113 | } ], 114 | }), 115 | }, 116 | ...extractConfig.use, 117 | ], 118 | }), 119 | },*/ 120 | { 121 | test: /style\.s?css$/, 122 | use: extractCSS.extract({ 123 | fallback: 'style-loader', // creates style nodes from JS strings 124 | use: [ 125 | { loader: 'css-loader' }, // translates CSS into CommonJS 126 | { loader: 'sass-loader' }, // compiles Sass to CSS 127 | ], 128 | }), 129 | }, 130 | { 131 | test: /block-library\.s?css$/, 132 | use: extractBLCSS.extract({ 133 | fallback: 'style-loader', // creates style nodes from JS strings 134 | use: [ 135 | { loader: 'css-loader' }, // translates CSS into CommonJS 136 | { loader: 'sass-loader' }, // compiles Sass to CSS 137 | ], 138 | }), 139 | }, 140 | ], 141 | }, 142 | plugins: [ 143 | extractCSS, 144 | extractBLCSS, 145 | // wrapping editor style with .gutenberg__editor class 146 | // new PostCssWrapper('./css/block-library/edit-blocks.css', '.gutenberg__editor'), 147 | // new StringReplacePlugin(), 148 | new CleanWebpackPlugin(['build']), 149 | ], 150 | stats: { 151 | children: false, 152 | }, 153 | }; 154 | --------------------------------------------------------------------------------