├── .babelrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── _build ├── config.json └── resolvers │ ├── resolve.lit.php │ ├── resolve.update.php │ └── resolve.whicheditor.php ├── assets └── components │ └── tinymcerte │ ├── connector.php │ └── mgr │ └── .gitignore ├── core └── components │ └── tinymcerte │ ├── composer.json │ ├── composer.lock │ ├── docs │ ├── changelog.md │ ├── license.md │ └── readme.md │ ├── elements │ └── plugins │ │ └── tinymcerte.plugin.php │ ├── lexicon │ ├── de │ │ ├── default.inc.php │ │ └── setting.inc.php │ ├── en │ │ ├── default.inc.php │ │ └── setting.inc.php │ ├── it │ │ ├── default.inc.php │ │ └── setting.inc.php │ └── ru │ │ ├── default.inc.php │ │ └── setting.inc.php │ ├── model │ └── tinymcerte │ │ └── tinymcerte.class.php │ ├── processors │ └── mgr │ │ └── resource │ │ ├── gettree.class.php │ │ └── search.class.php │ └── src │ ├── Plugins │ ├── Events │ │ ├── OnManagerPageBeforeRender.php │ │ ├── OnRichTextBrowserInit.php │ │ ├── OnRichTextEditorInit.php │ │ └── OnRichTextEditorRegister.php │ └── Plugin.php │ ├── Processors │ ├── GetTreeProcessor.php │ └── SearchProcessor.php │ └── TinyMCERTE.php ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── css │ └── tinymcerte.css └── js │ ├── Plugins │ ├── RegisterPlugins.js │ ├── modai │ │ └── modai.js │ └── modxlink │ │ ├── Data.js │ │ ├── Link.js │ │ ├── Unlink.js │ │ └── modxlink.js │ ├── browser.js │ ├── index.js │ └── modx │ ├── Browser.ext.js │ └── TinyMCE.ext.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["add-module-exports"], 3 | "presets": [ 4 | ["@babel/preset-env", { "targets": "> 0.25%, not dead" }] 5 | ] 6 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug report 11 | ### Summary 12 | Quick summary what's this issue about. 13 | 14 | ### Step to reproduce 15 | How to reproduce the issue, including custom code if needed. 16 | 17 | ### Observed behavior 18 | How it behaved after following steps above. 19 | 20 | ### Expected behavior 21 | How it should behave after following steps above. 22 | 23 | ### Environment 24 | MODX version, apache/nginx and version, mysql version, browser, etc. Any relevant information. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Feature request 11 | ### Summary 12 | Quick summary what's this feature request about. 13 | 14 | ### Why is it needed? 15 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 16 | 17 | ### Suggested solution(s) 18 | A clear and concise description of what you want to happen. 19 | 20 | ### Related issue(s)/PR(s) 21 | Let us know if this is related to any issue/pull request. 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What does it do? 2 | Describe the technical changes you did. 3 | 4 | ### Why is it needed? 5 | Describe the issue you are solving. 6 | 7 | ### Related issue(s)/PR(s) 8 | Let us know if this is related to any issue/pull request (see https://github.com/blog/1506-closing-issues-via-pull-requests) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Config files # 2 | ################ 3 | _build/build.config.php 4 | config.core.php 5 | 6 | # Packages # 7 | ############ 8 | _packages/*.* 9 | !_packages/.gitignore 10 | !_packages/*.zip 11 | 12 | # TinyMCE Source & Language # 13 | ################## 14 | /src/tinymce/ 15 | /src/*.zip 16 | 17 | # IDE files # 18 | ############# 19 | .idea 20 | .settings 21 | nbproject 22 | .project 23 | 24 | # OS generated files # 25 | ###################### 26 | .DS_Store 27 | .DS_Store? 28 | ._* 29 | .Spotlight-V100 30 | .Trashes 31 | ehthumbs.db 32 | Thumbs.db 33 | node_modules 34 | 35 | # Packages # 36 | ############ 37 | _packages 38 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyMCE Rich Text Editor 2 | 3 | TinyMCE 6 for MODX Revolution 4 | 5 | ## Features 6 | 7 | TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor. It 8 | allows non-technical users to format content without knowing how to code. This 9 | package is based on the TinyMCE 6. 10 | 11 | To build and install the package from source you have to use [Git Package 12 | Management (GPM)](https://github.com/TheBoxer/Git-Package-Management). The GitHub 13 | repository of TinyMCE Rich Text Editor contains a 14 | [config.json](https://github.com/modxcms/tinymce-rte/blob/main/_build/config.json) 15 | to build that package locally. Use this option, if you want to debug TinyMCE 16 | Rich Text Editor and/or contribute bugfixes and enhancements. 17 | 18 | To update the plugin run `npm install` to install the dependencies. Run 19 | `npm run build` to build the plugin. The source files are located in the `/src/` 20 | directory. The package is compiled using Node 20. 21 | 22 | To update the TinyMCE core, you have to change the `tinymcerte.tiny_url` system setting. 23 | 24 | ## Change Log 25 | https://github.com/modxcms/tinymce-rte/blob/master/core/components/tinymcerte/docs/changelog.md 26 | 27 | -------------------------------------------------------------------------------- /_build/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TinyMCE Rich Text Editor", 3 | "lowCaseName": "tinymcerte", 4 | "description": "TinyMCE 6", 5 | "author": "John Peca/Thomas Jakobi", 6 | "version": "3.0.4", 7 | "package": { 8 | "elements": { 9 | "plugins": [ 10 | { 11 | "name": "TinyMCE Rich Text Editor", 12 | "description": "TinyMCE Rich Text Editor runtime hooks - registers and includes javascripts on document edit pages", 13 | "file": "tinymcerte.plugin.php", 14 | "events": [ 15 | "OnManagerPageBeforeRender", 16 | "OnRichTextEditorRegister", 17 | "OnRichTextEditorInit", 18 | "OnRichTextBrowserInit" 19 | ] 20 | } 21 | ] 22 | }, 23 | "systemSettings": [ 24 | { 25 | "key": "plugins", 26 | "value": "advlist autoresize autolink lists charmap preview anchor visualblocks searchreplace code fullscreen insertdatetime media table image quickbars modxlink modai", 27 | "area": "tinymcerte.default" 28 | }, 29 | { 30 | "key": "menubar", 31 | "value": "file edit insert view format table tools", 32 | "area": "tinymcerte.default" 33 | }, 34 | { 35 | "key": "statusbar", 36 | "value": true, 37 | "type": "combo-boolean", 38 | "area": "tinymcerte.default" 39 | }, 40 | { 41 | "key": "image_advtab", 42 | "value": true, 43 | "type": "combo-boolean", 44 | "area": "tinymcerte.default" 45 | }, 46 | { 47 | "key": "object_resizing", 48 | "value": 1, 49 | "area": "tinymcerte.default" 50 | }, 51 | { 52 | "key": "paste_as_text", 53 | "value": false, 54 | "type": "combo-boolean", 55 | "area": "tinymcerte.default" 56 | }, 57 | { 58 | "key": "link_class_list", 59 | "value": "", 60 | "area": "tinymcerte.default" 61 | }, 62 | { 63 | "key": "browser_spellcheck", 64 | "type": "combo-boolean", 65 | "value": false, 66 | "area": "tinymcerte.default" 67 | }, 68 | { 69 | "key": "content_css", 70 | "value": "", 71 | "area": "tinymcerte.default" 72 | }, 73 | { 74 | "key": "image_class_list", 75 | "value": "", 76 | "area": "tinymcerte.default" 77 | }, 78 | { 79 | "key": "external_config", 80 | "value": "", 81 | "area": "tinymcerte.default" 82 | }, 83 | { 84 | "key": "skin", 85 | "value": "oxide", 86 | "area": "tinymcerte.default" 87 | }, 88 | { 89 | "key": "relative_urls", 90 | "value": true, 91 | "type": "combo-boolean", 92 | "area": "tinymcerte.default" 93 | }, 94 | { 95 | "key": "remove_script_host", 96 | "value": true, 97 | "type": "combo-boolean", 98 | "area": "tinymcerte.default" 99 | }, 100 | { 101 | "key": "valid_elements", 102 | "value": "", 103 | "area": "tinymcerte.default" 104 | }, 105 | { 106 | "key": "settings", 107 | "value": "", 108 | "area": "tinymcerte.default" 109 | }, 110 | { 111 | "key": "enable_link_list", 112 | "value": true, 113 | "type": "combo-boolean", 114 | "area": "tinymcerte.default" 115 | }, 116 | { 117 | "key": "links_across_contexts", 118 | "value": false, 119 | "type": "combo-boolean", 120 | "area": "tinymcerte.default" 121 | }, 122 | { 123 | "key": "max_height", 124 | "value": "500", 125 | "area": "tinymcerte.default" 126 | }, 127 | { 128 | "key": "min_height", 129 | "value": "100", 130 | "area": "tinymcerte.default" 131 | }, 132 | { 133 | "key": "insert_toolbar", 134 | "value": "image media quicktable modxlink modai_generate", 135 | "area": "tinymcerte.toolbar" 136 | }, 137 | { 138 | "key": "selection_toolbar", 139 | "value": "bold italic underline | modxlink | modai_enhance", 140 | "area": "tinymcerte.toolbar" 141 | }, 142 | { 143 | "key": "toolbar1", 144 | "value": "undo redo | styleselect | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | modxlink | image", 145 | "area": "tinymcerte.toolbar" 146 | }, 147 | { 148 | "key": "toolbar2", 149 | "value": "", 150 | "area": "tinymcerte.toolbar" 151 | }, 152 | { 153 | "key": "toolbar3", 154 | "value": "", 155 | "area": "tinymcerte.toolbar" 156 | }, 157 | { 158 | "key": "style_formats", 159 | "value": "[{\"title\": \"Headers\", \"items\": \"headers_format\"},{\"title\": \"Inline\", \"items\": \"inline_format\"},{\"title\": \"Blocks\", \"items\": \"blocks_format\"},{\"title\": \"Alignment\", \"items\": \"alignment_format\"}]", 160 | "type": "textarea", 161 | "area": "tinymcerte.style_formats" 162 | }, 163 | { 164 | "key": "headers_format", 165 | "value": "[{\"title\": \"Header 1\", \"format\": \"h1\"},{\"title\": \"Header 2\", \"format\": \"h2\"},{\"title\": \"Header 3\", \"format\": \"h3\"},{\"title\": \"Header 4\", \"format\": \"h4\"},{\"title\": \"Header 5\", \"format\": \"h5\"},{\"title\": \"Header 6\", \"format\": \"h6\"}]", 166 | "type": "textarea", 167 | "area": "tinymcerte.style_formats" 168 | }, 169 | { 170 | "key": "inline_format", 171 | "value": "[{\"title\": \"Bold\", \"icon\": \"bold\", \"format\": \"bold\"},{\"title\": \"Italic\", \"icon\": \"italic\", \"format\": \"italic\"},{\"title\": \"Underline\", \"icon\": \"underline\", \"format\": \"underline\"},{\"title\": \"Strikethrough\", \"icon\": \"strike-through\", \"format\": \"strikethrough\"},{\"title\": \"Superscript\", \"icon\": \"superscript\", \"format\": \"superscript\"},{\"title\": \"Subscript\", \"icon\": \"subscript\", \"format\": \"subscript\"},{\"title\": \"Code\", \"icon\": \"sourcecode\", \"format\": \"code\"}]", 172 | "type": "textarea", 173 | "area": "tinymcerte.style_formats" 174 | }, 175 | { 176 | "key": "blocks_format", 177 | "value": "[{\"title\": \"Paragraph\", \"format\": \"p\"},{\"title\": \"Blockquote\", \"format\": \"blockquote\"},{\"title\": \"Div\", \"format\": \"div\"},{\"title\": \"Pre\", \"format\": \"pre\"}]", 178 | "type": "textarea", 179 | "area": "tinymcerte.style_formats" 180 | }, 181 | { 182 | "key": "alignment_format", 183 | "value": "[{\"title\": \"Left\", \"icon\": \"align-left\", \"format\": \"alignleft\"},{\"title\": \"Center\", \"icon\": \"align-center\", \"format\": \"aligncenter\"},{\"title\": \"Right\", \"icon\": \"align-right\", \"format\": \"alignright\"},{\"title\": \"Justify\", \"icon\": \"align-justify\", \"format\": \"alignjustify\"}]", 184 | "type": "textarea", 185 | "area": "tinymcerte.style_formats" 186 | }, 187 | { 188 | "key": "style_formats_merge", 189 | "value": false, 190 | "type": "combo-boolean", 191 | "area": "tinymcerte.style_formats" 192 | }, 193 | { 194 | "key": "modai.global.text.modify_prompts", 195 | "area": "modai", 196 | "type": "textarea", 197 | "value": "[{\"label\":\"Proofread\",\"prompt\":\"Proofread and fix any spelling or grammar mistakes\"},{\"label\":\"Clarify\",\"prompt\":\"Improve this copy to eliminate unclear thoughts or awkward phrasing\"},{\"label\":\"Length\",\"prompts\":[{\"label\":\"Add a Paragraph\",\"prompt\":\"- Add a paragraph to this copy.\\n- You can rewrite the other copy if it enahances the copy\\n- Format it with HTML markup so it will work in the TinyMCE editor when pasted in.\"},{\"label\":\"Add a Sentence\",\"prompt\":\"- Add a sentence to this copy that will improve it\\n- You can rewrite the other copy if it enahances the copy\\n- Format it with HTML markup so it will work in the TinyMCE editor when pasted in.\"},{\"label\":\"Remove a Sentence\",\"prompt\":\"- Remove a sentence from this copy. Format it with HTML markup so it will work in the TinyMCE editor when pasted in.\"},{\"label\":\"Cut in Half\",\"prompt\":\"- Condense this copy to roughly half of its current length\\n- Maintain important concepts and key topics\\n- Combine or eliminate paragraphs if it makes sense and helps with readability\\n- Format it with HTML markup so it will work in the TinyMCE editor when pasted in.\"}]},{\"label\":\"Reading Level\",\"prompts\":[{\"label\":\"Elementary school\",\"prompt\":\"Rewrite this copy to ensure it is readable at an Elementary school reading level.\"},{\"label\":\"Middle school\",\"prompt\":\"Rewrite this copy to ensure it is readable at a Middle school reading level.\"},{\"label\":\"High school\",\"prompt\":\"Rewrite this copy to ensure it is readable at a High school reading level.\"},{\"label\":\"College\",\"prompt\":\"Rewrite this copy to ensure it is readable at a College reading level.\"}]},{\"label\":\"Change Tone\",\"prompts\":[{\"label\":\"Formal\",\"prompt\":\"Rewrite this with a formal tone.\"},{\"label\":\"Conversational\",\"prompt\":\"Rewrite this with a casual, conversational tone.\"},{\"label\":\"Professional\",\"prompt\":\"Rewrite this with a professional tone.\"},{\"label\":\"Humorous\",\"prompt\":\"Rewrite this with a humorous tone.\"},{\"label\":\"Persuasive\",\"prompt\":\"Rewrite this with an persuasive tone.\"}]},{\"label\":\"Change Style\",\"prompts\":[{\"label\":\"Business\",\"prompt\":\"Rewrite this in a Business style\"},{\"label\":\"Technical\",\"prompt\":\"Rewrite this in a Technical style\"},{\"label\":\"AP Style Manual\",\"prompt\":\"Reformat this to adhere to the AP Style Manual\"},{\"label\":\"Chicago Style Manual\",\"prompt\":\"Reformat this to adhere to the Chicago Style Manual\"},{\"label\":\"David Olgivy\",\"prompt\":\"Rewrite this in the style of the expert ad copywriter David Olgivy\"}]},{\"label\":\"Translate\",\"prompts\":[{\"label\":\"Chinese\",\"prompt\":\"Translate this to Chinese\"},{\"label\":\"Dutch\",\"prompt\":\"Translate this to Dutch\"},{\"label\":\"English (CA)\",\"prompt\":\"Localise or translate this to Canadian English\"},{\"label\":\"English (UK)\",\"prompt\":\"Localise or translate this to British English\"},{\"label\":\"English (US)\",\"prompt\":\"Localize or translate this to American English\"},{\"label\":\"English (RN)\",\"prompt\":\"Localize this to the writing style of someone who lives in rural America and speaks with a lot of slang and colloquialisms like \\\"y'all\\\" instead of \\\",you all\\\", and \\\"fixin'\\\" instead of \\\"about to\\\"\"},{\"label\":\"French\",\"prompt\":\"Translate this to French\"},{\"label\":\"German\",\"prompt\":\"Translate this to German\"},{\"label\":\"Italian\",\"prompt\":\"Translate this to Italian\"},{\"label\":\"Japanese\",\"prompt\":\"Translate this to Japanese\"},{\"label\":\"Korean\",\"prompt\":\"Translate this to Korean\"},{\"label\":\"Portuguese (BR)\",\"prompt\":\"Translate this to Brazillian Portugese\"},{\"label\":\"Portuguese (PT)\",\"prompt\":\"Translate this to European Portugese\"},{\"label\":\"Russian\",\"prompt\":\"Translate this to Russian\"},{\"label\":\"Spanish\",\"prompt\":\"Translate this to Spanish\"}]}]" 198 | }, 199 | { 200 | "key": "modai.global.text.base_prompt", 201 | "area": "modai", 202 | "type": "textarea", 203 | "value": "- This is not a conversation, do not include justifcation, suggestions, or explanation\n- The output should be usable as is, without requiring editing or modification\n- Format it with HTML markup so it will work in the TinyMCE rich text editor" 204 | }, 205 | { 206 | "key": "tiny_url", 207 | "area": "tinymcerte.default", 208 | "value": "{tinymcerte.assets_url}mgr/tinymce/tinymce.min.js" 209 | }, 210 | { 211 | "key": "lit", 212 | "area": "tinymcerte.default", 213 | "value": "0" 214 | } 215 | ] 216 | }, 217 | "dependencies": [ 218 | { 219 | "name": "php", 220 | "version": ">=5.6" 221 | }, 222 | { 223 | "name": "modx", 224 | "version": ">=2.7" 225 | } 226 | ], 227 | "build": { 228 | "readme": "docs/readme.md", 229 | "license": "docs/license.md", 230 | "changelog": "docs/changelog.md", 231 | "resolver": { 232 | "after": [ 233 | "resolve.lit.php", 234 | "resolve.update.php", 235 | "resolve.whicheditor.php" 236 | ] 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /_build/resolvers/resolve.lit.php: -------------------------------------------------------------------------------- 1 | xpdo) { 4 | /** @var modX $modx */ 5 | $modx =& $object->xpdo; 6 | 7 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 8 | case xPDOTransport::ACTION_INSTALL: 9 | case xPDOTransport::ACTION_UPGRADE: 10 | $setting = $modx->getObject('modSystemSetting', ['key' => 'tinymcerte.lit']); 11 | if (!$setting) { 12 | $setting = $modx->newObject('modSystemSetting'); 13 | $setting->fromArray([ 14 | 'key' => 'tinymcerte.lit', 15 | 'namespace' => 'tinymcerte', 16 | 'xtype' => 'textfield', 17 | 'area' => 'tinymcerte.default', 18 | 'editedon' => time(), 19 | 'editedby' => 0, 20 | ]); 21 | } 22 | $setting->set('value', time()); 23 | $setting->save(); 24 | break; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /_build/resolvers/resolve.update.php: -------------------------------------------------------------------------------- 1 | xpdo) { 12 | if (!function_exists('addToSetting')) { 13 | /** 14 | * @param modX $modx 15 | * @param string $key 16 | * @param string $old 17 | * @param string $new 18 | */ 19 | function addToSetting($modx, $key, $add, $separator = ' ') 20 | { 21 | /** @var modSystemSetting $setting */ 22 | $setting = $modx->getObject('modSystemSetting', [ 23 | 'key' => $key 24 | ]); 25 | if ($setting && strpos($setting->get('value'), $add) === false) { 26 | $array = explode($separator, $setting->get('value')); 27 | $array[] = $add; 28 | $setting->set('value', implode(' ', $array)); 29 | $setting->save(); 30 | } 31 | } 32 | } 33 | 34 | if (!function_exists('removeFromSetting')) { 35 | /** 36 | * @param modX $modx 37 | * @param string $key 38 | * @param string $old 39 | * @param string $new 40 | */ 41 | function removeFromSetting($modx, $key, $remove, $separator = ' ') 42 | { 43 | /** @var modSystemSetting $setting */ 44 | $setting = $modx->getObject('modSystemSetting', [ 45 | 'key' => $key 46 | ]); 47 | if ($setting && strpos($setting->get('value'), $remove) !== false) { 48 | $array = explode($separator, $setting->get('value')); 49 | $array = array_diff($array, [$remove]); 50 | $setting->set('value', implode(' ', $array)); 51 | $setting->save(); 52 | } 53 | } 54 | } 55 | 56 | if (!function_exists('changeSetting')) { 57 | /** 58 | * @param modX $modx 59 | * @param string $key 60 | * @param string $old 61 | * @param string $new 62 | */ 63 | function changeSetting($modx, $key, $old, $new) 64 | { 65 | /** @var modSystemSetting $setting */ 66 | $setting = $modx->getObject('modSystemSetting', [ 67 | 'key' => $key 68 | ]); 69 | if ($setting) { 70 | $setting->set('value', str_replace($old, $new, $setting->get('value'))); 71 | $setting->save(); 72 | } 73 | } 74 | } 75 | 76 | if (!function_exists('changeSettingArea')) { 77 | /** 78 | * @param modX $modx 79 | * @param string $old 80 | * @param string $new 81 | */ 82 | function changeSettingArea($modx, $old, $new) 83 | { 84 | /** @var modSystemSetting[] $settings */ 85 | $settings = $modx->getIterator('modSystemSetting', [ 86 | 'namespace' => 'tinymcerte', 87 | 'area' => $old 88 | ]); 89 | foreach ($settings as $setting) { 90 | $setting->set('area', $new); 91 | $setting->save(); 92 | } 93 | } 94 | } 95 | 96 | if (!function_exists('recursiveRemoveFolder')) { 97 | /** 98 | * @param string $dir 99 | * @return bool 100 | */ 101 | function recursiveRemoveFolder($dir) 102 | { 103 | $files = array_diff(scandir($dir), ['.', '..']); 104 | foreach ($files as $file) { 105 | (is_dir("$dir/$file")) ? recursiveRemoveFolder($dir . '/' . $file) : unlink($dir . '/' . $file); 106 | } 107 | return rmdir($dir); 108 | } 109 | } 110 | 111 | if (!function_exists('cleanupFolders')) { 112 | /** 113 | * @param modX $modx 114 | * @param string $corePath 115 | * @param string $assetsPath 116 | * @param array $cleanup 117 | */ 118 | function cleanupFolders($modx, $corePath, $assetsPath, $cleanup) 119 | { 120 | $paths = [ 121 | 'core' => $corePath, 122 | 'assets' => $assetsPath, 123 | ]; 124 | $countFiles = 0; 125 | $countFolders = 0; 126 | foreach ($cleanup as $folder => $files) { 127 | foreach ($files as $file) { 128 | $legacyFile = $paths[$folder] . $file; 129 | if (file_exists($legacyFile)) { 130 | if (is_dir($legacyFile)) { 131 | recursiveRemoveFolder($legacyFile); 132 | $countFolders++; 133 | } else { 134 | unlink($legacyFile); 135 | $countFiles++; 136 | } 137 | } 138 | } 139 | } 140 | if ($countFolders || $countFiles) { 141 | $modx->log(xPDO::LOG_LEVEL_INFO, 'Removed ' . $countFiles . ' legacy files and ' . $countFolders . ' legacy folders of TinyMCE 1.x'); 142 | } 143 | } 144 | } 145 | 146 | if (!function_exists('cleanupPlugin')) { 147 | /** 148 | * @param modX $modx 149 | * @param string $name 150 | */ 151 | function cleanupPlugin($modx, $name) 152 | { 153 | /** @var modPlugin $plugin */ 154 | $plugin = $modx->getObject('modPlugin', [ 155 | 'name' => $name 156 | ]); 157 | if ($plugin) { 158 | $plugin->remove(); 159 | } 160 | } 161 | } 162 | 163 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 164 | case xPDOTransport::ACTION_INSTALL: 165 | case xPDOTransport::ACTION_UPGRADE: 166 | /** @var modX $modx */ 167 | $modx =& $object->xpdo; 168 | // http://forums.modx.com/thread/88734/package-version-check#dis-post-489104 169 | $c = $modx->newQuery('transport.modTransportPackage'); 170 | $c->where([ 171 | 'workspace' => 1, 172 | "(SELECT 173 | `signature` 174 | FROM {$modx->getTableName('transport.modTransportPackage')} AS `latestPackage` 175 | WHERE `latestPackage`.`package_name` = `modTransportPackage`.`package_name` 176 | ORDER BY 177 | `latestPackage`.`version_major` DESC, 178 | `latestPackage`.`version_minor` DESC, 179 | `latestPackage`.`version_patch` DESC, 180 | IF(`release` = '' OR `release` = 'ga' OR `release` = 'pl','z',`release`) DESC, 181 | `latestPackage`.`release_index` DESC 182 | LIMIT 1,1) = `modTransportPackage`.`signature`", 183 | ]); 184 | $c->where([ 185 | [ 186 | 'modTransportPackage.package_name' => 'tinymcerte', 187 | 'OR:modTransportPackage.package_name:=' => 'TinyMCE Rich Text Editor', 188 | ], 189 | 'installed:IS NOT' => null 190 | ]); 191 | /** @var modTransportPackage $oldPackage */ 192 | $oldPackage = $modx->getObject('transport.modTransportPackage', $c); 193 | $corePath = $modx->getOption('tinymcerte.core_path', null, $modx->getOption('coee_path', null, MODX_CORE_PATH) . 'components/tinymcerte/'); 194 | $assetsPath = $modx->getOption('tinymcerte.assets_path', null, $modx->getOption('assets_path', null, MODX_ASSETS_PATH) . 'components/tinymcerte/'); 195 | 196 | if ($oldPackage && $oldPackage->compareVersion('1.1.0-pl', '>')) { 197 | changeSetting($modx, 'tinymcerte.plugins', ' link ', ' modxlink '); 198 | } 199 | 200 | if ($oldPackage && $oldPackage->compareVersion('1.1.1-pl', '>')) { 201 | changeSetting($modx, 'tinymcerte.plugins', ' image ', ' modximage '); 202 | } 203 | 204 | if ($oldPackage && $oldPackage->compareVersion('2.0.0-pl', '>')) { 205 | changeSetting($modx, 'tinymcerte.skin', 'lightgray', 'modx'); 206 | changeSetting($modx, 'tinymcerte.inline_format', '"icon": "strikethrough"', '"icon": "strike-through"'); 207 | changeSetting($modx, 'tinymcerte.inline_format', '"icon": "code"', '"icon": "sourcecode"'); 208 | changeSetting($modx, 'tinymcerte.alignment_format', '"icon": "alignleft"', '"icon": "align-left"'); 209 | changeSetting($modx, 'tinymcerte.alignment_format', '"icon": "aligncenter"', '"icon": "align-center"'); 210 | changeSetting($modx, 'tinymcerte.alignment_format', '"icon": "alignright"', '"icon": "align-right"'); 211 | changeSetting($modx, 'tinymcerte.alignment_format', '"icon": "alignjustify"', '"icon": "align-justify"'); 212 | removeFromSetting($modx, 'tinymcerte.plugins', 'contextmenu'); 213 | changeSettingArea($modx, 'default', 'tinymcerte.default'); 214 | 215 | $cleanup = [ 216 | 'core' => [ 217 | 'docs/changelog.txt', 218 | 'docs/license.txt', 219 | 'docs/readme.txt', 220 | ], 221 | 'assets' => [ 222 | 'js/mgr/extras/browser.js', 223 | 'js/vendor/autocomplete.js', 224 | ] 225 | ]; 226 | cleanupFolders($modx, $corePath, $assetsPath, $cleanup); 227 | cleanupPlugin($modx, 'TinyMCERTE'); 228 | } 229 | 230 | if ($oldPackage && $oldPackage->compareVersion('2.0.6-pl', '>')) { 231 | addToSetting($modx, 'tinymcerte.plugins', 'autoresize'); 232 | } 233 | 234 | if ($oldPackage && $oldPackage->compareVersion('2.1.0-pl', '>')) { 235 | addToSetting($modx, 'tinymcerte.plugins', 'modai'); 236 | addToSetting($modx, 'tinymcerte.toolbar1', '| modai_generate modai_generate_image modai_enhance'); 237 | } 238 | 239 | if ($oldPackage && $oldPackage->compareVersion('2.1.1-pl', '>')) { 240 | removeFromSetting($modx, 'tinymcerte.toolbar1', 'modai_generate_image'); 241 | } 242 | 243 | if ($oldPackage && $oldPackage->compareVersion('3.0.2-pl', '>')) { 244 | changeSetting($modx, 'tinymcerte.tiny_url', 'https://cdnjs.cloudflare.com/ajax/libs/tinymce/6.8.5/tinymce.min.js', '{tinymcerte.assets_url}mgr/tinymce/tinymce.min.js'); 245 | removeFromSetting($modx, 'tinymcerte.plugins', 'paste'); 246 | removeFromSetting($modx, 'tinymcerte.plugins', 'print'); 247 | } 248 | 249 | if ($oldPackage && $oldPackage->compareVersion('3.0.3-pl', '>')) { 250 | removeFromSetting($modx, 'tinymcerte.plugins', 'modximage'); 251 | addToSetting($modx, 'tinymcerte.plugins', 'quickbars'); 252 | changeSetting($modx, 'tinymcerte.toolbar1', 'link', 'modxlink'); 253 | changeSetting($modx, 'tinymcerte.toolbar2', 'link', 'modxlink'); 254 | changeSetting($modx, 'tinymcerte.toolbar3', 'link', 'modxlink'); 255 | } 256 | 257 | if ($oldPackage && $oldPackage->compareVersion('3.0.4-pl', '>')) { 258 | changeSetting($modx, 'tinymcerte.skin', 'modx', 'oxide'); 259 | } 260 | 261 | break; 262 | } 263 | } 264 | return true; 265 | -------------------------------------------------------------------------------- /_build/resolvers/resolve.whicheditor.php: -------------------------------------------------------------------------------- 1 | xpdo) { 12 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 13 | case xPDOTransport::ACTION_INSTALL: 14 | case xPDOTransport::ACTION_UPGRADE: 15 | $object->xpdo->log(xPDO::LOG_LEVEL_INFO, 'Attempting to set which_editor setting to TinyMCE RTE.'); 16 | $setting = $object->xpdo->getObject('modSystemSetting', [ 17 | 'key' => 'which_editor' 18 | ]); 19 | if ($setting) { 20 | $setting->set('value', 'TinyMCE RTE'); 21 | $setting->save(); 22 | } 23 | unset($setting); 24 | 25 | $object->xpdo->log(xPDO::LOG_LEVEL_INFO, 'Attempting to set use_editor setting to on.'); 26 | $setting = $object->xpdo->getObject('modSystemSetting', [ 27 | 'key' => 'use_editor', 28 | 'value' => '0' 29 | ]); 30 | if ($setting) { 31 | $setting->set('value', 1); 32 | $setting->save(); 33 | } 34 | unset($setting); 35 | 36 | break; 37 | case xPDOTransport::ACTION_UNINSTALL: 38 | $setting = $object->xpdo->getObject('modSystemSetting', [ 39 | 'key' => 'which_editor', 40 | 'value' => 'TinyMCE RTE' 41 | ]); 42 | if ($setting) { 43 | $setting->set('value', ''); 44 | $setting->save(); 45 | } 46 | 47 | break; 48 | } 49 | } 50 | return true; 51 | -------------------------------------------------------------------------------- /assets/components/tinymcerte/connector.php: -------------------------------------------------------------------------------- 1 | getOption('tinymcerte.core_path', null, $modx->getOption('core_path', null, MODX_CORE_PATH) . 'components/tinymcerte/'); 16 | /** @var TinyMCERTE $tinymcerte */ 17 | $tinymcerte = $modx->getService('tinymcerte', 'TinyMCERTE', $corePath . 'model/tinymcerte/', [ 18 | 'core_path' => $corePath 19 | ]); 20 | 21 | $processorsPath = $tinymcerte->getOption('processorsPath'); 22 | 23 | // Handle request 24 | $modx->request->handleRequest([ 25 | 'processors_path' => $processorsPath, 26 | 'location' => '' 27 | ]); 28 | -------------------------------------------------------------------------------- /assets/components/tinymcerte/mgr/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /core/components/tinymcerte/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modxcms/tinymcerte", 3 | "description": "Calendar management for MODX", 4 | "authors": [ 5 | { 6 | "name": "John Peca", 7 | "email": "pecajan@gmail.com", 8 | "homepage": "http://www.bxr.cz" 9 | }, 10 | { 11 | "name": "Thomas Jakobi", 12 | "email": "office@treehillstudio.com", 13 | "homepage": "https://treehillstudio.com" 14 | } 15 | ], 16 | "config": { 17 | "platform": { 18 | "php": "5.6" 19 | } 20 | }, 21 | "require": { 22 | "php": ">=5.6", 23 | "ext-json": "*", 24 | "ext-pdo": "*" 25 | }, 26 | "license": "GPL-2.0-or-later", 27 | "autoload": { 28 | "psr-4": { 29 | "TinyMCERTE\\": "src/" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/components/tinymcerte/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "ec6f56369e52e74b3c8c75a927fecee1", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": { 16 | "php": ">=5.6", 17 | "ext-json": "*", 18 | "ext-pdo": "*" 19 | }, 20 | "platform-dev": [], 21 | "platform-overrides": { 22 | "php": "5.6" 23 | }, 24 | "plugin-api-version": "2.1.0" 25 | } 26 | -------------------------------------------------------------------------------- /core/components/tinymcerte/docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [3.0.4] - 2025-06-06 9 | 10 | ### Changed 11 | 12 | - Fix z-index issue with MIGX 13 | - Fix typo on tinymcerte.skin system setting 14 | 15 | ## [3.0.3] - 2025-06-04 16 | 17 | ### Changed 18 | 19 | - Automatically remove modximage from plugins if still installed 20 | 21 | ## [3.0.2] - 2025-06-03 22 | 23 | ### Changed 24 | 25 | - Added cache busting 26 | 27 | ## [3.0.1] - 2025-05-29 28 | 29 | ### Changed 30 | 31 | - Switch to local instance of TinyMCE 6 32 | 33 | ## [3.0.0] - 2025-05-29 34 | 35 | ### Changed 36 | 37 | - Refactored the project to use TinyMCE 6 38 | 39 | ## [2.1.1] - 2025-03-21 40 | 41 | ### Changed 42 | 43 | - Update to the newest modAI API 44 | 45 | ## [2.1.0] - 2025-02-28 46 | 47 | ### Added 48 | 49 | - Add support for modAI 50 | 51 | ## [2.0.9] - 2022-09-28 52 | 53 | ### Fixed 54 | 55 | - Fix `max_height` system setting not cast right 56 | 57 | ## [2.0.8] - 2022-08-01 58 | 59 | ### Added 60 | 61 | - Add a default `max_height` system setting to restrict the height of the editor with an enabled `autoresize` plugin 62 | 63 | ### Fixed 64 | 65 | - Fill the TinyMCE `document_base_url` with the `site_url` context setting instead of the `site_url` system setting [#121] 66 | 67 | ## [2.0.7] - 2022-03-18 68 | 69 | ### Fixed 70 | 71 | - Avoid and log an invalid TinyMCE configuration 72 | - Get the right manager language in MODX 3.x 73 | 74 | ## [2.0.6] - 2022-02-16 75 | 76 | ### Added 77 | 78 | - Change `lightgray` skin system setting to `modx` during an update 79 | - Add `autoresize` plugin per default 80 | 81 | ## [2.0.5] - 2021-12-27 82 | 83 | ### Added 84 | 85 | - Unescape escaped regex strings (i.e. to allow javascript regex filters in an external config) [#117] 86 | 87 | ### Fix 88 | 89 | - Escape MySQL reserved word rank [#119] 90 | 91 | ## [2.0.4] - 2021-12-03 92 | 93 | ### Added 94 | 95 | - Load the TinyMCE configuration even if the resource is not using a rich text editor 96 | - Allow drop of MODX elements to the editor content in richtext template variables 97 | 98 | ### Fix 99 | 100 | - Fix an uncaught type error when the current resource has richtext disabled and uses ContentBlocks 101 | 102 | ## [2.0.3] - 2021-10-01 103 | 104 | ### Fix 105 | 106 | - Fix setting the link text after selecting a resource 107 | 108 | ## [2.0.2] - 2021-09-30 109 | 110 | ### Changed 111 | 112 | - Update TinyMCE to 5.9.2 113 | - Restored compatibility for PHP 7.1 and less 114 | 115 | ## [2.0.1] - 2021-05-14 116 | 117 | ### Changed 118 | 119 | - Update TinyMCE to 5.8.0 120 | - Improve the configuration output in the manager html code 121 | 122 | ### Fixed 123 | 124 | - Compatibility with moregallery and Collections 125 | 126 | ## [2.0.0] - 2021-03-19 127 | 128 | ### Added 129 | 130 | - MODX skintool.json for http://skin.tiny.cloud/t5/ 131 | - MODX 3 compatibility 132 | - link_list_enable system setting 133 | 134 | ### Changed 135 | 136 | - Upgrade TinyMCE to 5 137 | - Refactored modxlink TinyMCE plugin to use the nested link_list option 138 | - Refactored modximage TinyMCE plugin 139 | - Recursive merge the external config with the config 140 | - Remove the deprecated file_browser_callback and use the file_picker_callback 141 | - Allow direct JSON based style_formats items 142 | 143 | ## [1.4.0] - 2020-09-11 144 | 145 | ### Added 146 | 147 | - Build the modx skin with the internal tinymce grunt workflow 148 | 149 | ### Changed 150 | 151 | - Extend/Fix the modx skin styles 152 | - Fix an issue with the table tool buttons 153 | 154 | ## [1.3.4] - 2020-08-12 155 | 156 | ### Added 157 | 158 | - The modx skin extends the lightgray skin, that way the css changes in the lightgray skin are available after a TinyMCE update 159 | 160 | ### Changed 161 | 162 | - Some lexicon changes/improvements 163 | - Upgrade TinyMCE to 4.9.11 164 | 165 | ### Removed 166 | 167 | - Removed some unnecessary files 168 | 169 | ## [1.3.3] - 2020-02-04 170 | 171 | ### Changed 172 | 173 | - Bugfix for not using full width when the editor is moved to a new tab [#86] 174 | - Upgrade TinyMCE to 4.9.7 175 | 176 | ## [1.3.2] - 2019-06-13 177 | 178 | ### Changed 179 | 180 | - Bugfix for showing only an english user interface 181 | 182 | ## [1.3.1] - 2019-06-05 183 | 184 | ### Added 185 | 186 | - Added field displaying resource pagetitle of MODX link [#83] 187 | - Added image_caption option for TinyMCE [#60] 188 | 189 | ### Changed 190 | 191 | - Expanding the locale list [#82] 192 | - Get settings from a JSON encoded array in tinymcerte.settings system setting 193 | - Make the entity_encoding configurable [#79] 194 | - Upgrade TinyMCE to 4.9.4 195 | 196 | ## [1.3.0] - 2019-05-22 197 | 198 | ### Added 199 | 200 | - Manage TinyMCE release download by npm 201 | - Add Gruntfile.js that copies the current release of TinyMCE to the corresponding folders 202 | - Add version info to the registered assets 203 | - Adding Russian translation 204 | 205 | ### Changed 206 | 207 | - Upgrade TinyMCE to 4.8.3 208 | 209 | ## [1.2.1] - 2017-12-16 210 | 211 | ### Added 212 | 213 | - Added language strings for the system settings added in 1.2.0 214 | 215 | ### Changed 216 | 217 | - Escaped special HTML chars in the modxlink plugin 218 | - Fixing 'Media browser does not close when clicking on close' 219 | 220 | ## [1.2.0] - 2017-05-21 221 | 222 | ### Added 223 | 224 | - Added `relative_urls` & `remove_script_host` settings 225 | - Added system setting to define 'valid_elements' 226 | - Added 'links_across_contexts' setting to limit links to the current context resources 227 | - Added support for configured default Media Source in context settings 228 | - CMPs can now pass any TinyMCE configuration property using the `OnRichTextEditorInit` system event 229 | 230 | ### Changed 231 | 232 | - Plugin now makes use of `modManagerController::addJavascript` instead of `modX::regClientStartupScript` 233 | - Upgraded to TinyMCE 4.5.7 234 | 235 | ## [1.1.1] - 2016-01-20 236 | 237 | ### Added 238 | 239 | - Add tel: prefix 240 | - Add modximage - left/right image positioning 241 | - Add modx skin (Credits goes to fourroses666) 242 | - Add skin system setting 243 | 244 | ### Changed 245 | 246 | - Allow base path parsing in the external_config system setting 247 | - Sync tinymce and textarea 248 | 249 | ## [1.1.0] - 2015-07-13 250 | 251 | ### Added 252 | 253 | - Add autocomplete search for links 254 | - Add external config 255 | - Support for link classes 256 | 257 | ## [1.0.0] - 2015-02-23 258 | 259 | ### Added 260 | 261 | - Initial release 262 | -------------------------------------------------------------------------------- /core/components/tinymcerte/docs/license.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | -------------------------- 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 6 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | Preamble 12 | -------- 13 | 14 | The licenses for most software are designed to take away your 15 | freedom to share and change it. By contrast, the GNU General Public 16 | License is intended to guarantee your freedom to share and change free 17 | software--to make sure the software is free for all its users. This 18 | General Public License applies to most of the Free Software 19 | Foundation's software and to any other program whose authors commit to 20 | using it. (Some other Free Software Foundation software is covered by 21 | the GNU Library General Public License instead.) You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not 25 | price. Our General Public Licenses are designed to make sure that you 26 | have the freedom to distribute copies of free software (and charge for 27 | this service if you wish), that you receive source code or can get it 28 | if you want it, that you can change the software or use pieces of it 29 | in new free programs; and that you know you can do these things. 30 | 31 | To protect your rights, we need to make restrictions that forbid 32 | anyone to deny you these rights or to ask you to surrender the rights. 33 | These restrictions translate to certain responsibilities for you if you 34 | distribute copies of the software, or if you modify it. 35 | 36 | For example, if you distribute copies of such a program, whether 37 | gratis or for a fee, you must give the recipients all the rights that 38 | you have. You must make sure that they, too, receive or can get the 39 | source code. And you must show them these terms so they know their 40 | rights. 41 | 42 | We protect your rights with two steps: (1) copyright the software, and 43 | (2) offer you this license which gives you legal permission to copy, 44 | distribute and/or modify the software. 45 | 46 | Also, for each author's protection and ours, we want to make certain 47 | that everyone understands that there is no warranty for this free 48 | software. If the software is modified by someone else and passed on, we 49 | want its recipients to know that what they have is not the original, so 50 | that any problems introduced by others will not reflect on the original 51 | authors' reputations. 52 | 53 | Finally, any free program is threatened constantly by software 54 | patents. We wish to avoid the danger that redistributors of a free 55 | program will individually obtain patent licenses, in effect making the 56 | program proprietary. To prevent this, we have made it clear that any 57 | patent must be licensed for everyone's free use or not licensed at all. 58 | 59 | The precise terms and conditions for copying, distribution and 60 | modification follow. 61 | 62 | 63 | GNU GENERAL PUBLIC LICENSE 64 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 65 | --------------------------------------------------------------- 66 | 67 | 0. This License applies to any program or other work which contains 68 | a notice placed by the copyright holder saying it may be distributed 69 | under the terms of this General Public License. The "Program", below, 70 | refers to any such program or work, and a "work based on the Program" 71 | means either the Program or any derivative work under copyright law: 72 | that is to say, a work containing the Program or a portion of it, 73 | either verbatim or with modifications and/or translated into another 74 | language. (Hereinafter, translation is included without limitation in 75 | the term "modification".) Each licensee is addressed as "you". 76 | 77 | Activities other than copying, distribution and modification are not 78 | covered by this License; they are outside its scope. The act of 79 | running the Program is not restricted, and the output from the Program 80 | is covered only if its contents constitute a work based on the 81 | Program (independent of having been made by running the Program). 82 | Whether that is true depends on what the Program does. 83 | 84 | 1. You may copy and distribute verbatim copies of the Program's 85 | source code as you receive it, in any medium, provided that you 86 | conspicuously and appropriately publish on each copy an appropriate 87 | copyright notice and disclaimer of warranty; keep intact all the 88 | notices that refer to this License and to the absence of any warranty; 89 | and give any other recipients of the Program a copy of this License 90 | along with the Program. 91 | 92 | You may charge a fee for the physical act of transferring a copy, and 93 | you may at your option offer warranty protection in exchange for a fee. 94 | 95 | 2. You may modify your copy or copies of the Program or any portion 96 | of it, thus forming a work based on the Program, and copy and 97 | distribute such modifications or work under the terms of Section 1 98 | above, provided that you also meet all of these conditions: 99 | 100 | a) You must cause the modified files to carry prominent notices 101 | stating that you changed the files and the date of any change. 102 | 103 | b) You must cause any work that you distribute or publish, that in 104 | whole or in part contains or is derived from the Program or any 105 | part thereof, to be licensed as a whole at no charge to all third 106 | parties under the terms of this License. 107 | 108 | c) If the modified program normally reads commands interactively 109 | when run, you must cause it, when started running for such 110 | interactive use in the most ordinary way, to print or display an 111 | announcement including an appropriate copyright notice and a 112 | notice that there is no warranty (or else, saying that you provide 113 | a warranty) and that users may redistribute the program under 114 | these conditions, and telling the user how to view a copy of this 115 | License. (Exception: if the Program itself is interactive but 116 | does not normally print such an announcement, your work based on 117 | the Program is not required to print an announcement.) 118 | 119 | These requirements apply to the modified work as a whole. If 120 | identifiable sections of that work are not derived from the Program, 121 | and can be reasonably considered independent and separate works in 122 | themselves, then this License, and its terms, do not apply to those 123 | sections when you distribute them as separate works. But when you 124 | distribute the same sections as part of a whole which is a work based 125 | on the Program, the distribution of the whole must be on the terms of 126 | this License, whose permissions for other licensees extend to the 127 | entire whole, and thus to each and every part regardless of who wrote it. 128 | 129 | Thus, it is not the intent of this section to claim rights or contest 130 | your rights to work written entirely by you; rather, the intent is to 131 | exercise the right to control the distribution of derivative or 132 | collective works based on the Program. 133 | 134 | In addition, mere aggregation of another work not based on the Program 135 | with the Program (or with a work based on the Program) on a volume of 136 | a storage or distribution medium does not bring the other work under 137 | the scope of this License. 138 | 139 | 3. You may copy and distribute the Program (or a work based on it, 140 | under Section 2) in object code or executable form under the terms of 141 | Sections 1 and 2 above provided that you also do one of the following: 142 | 143 | a) Accompany it with the complete corresponding machine-readable 144 | source code, which must be distributed under the terms of Sections 145 | 1 and 2 above on a medium customarily used for software interchange; or, 146 | 147 | b) Accompany it with a written offer, valid for at least three 148 | years, to give any third party, for a charge no more than your 149 | cost of physically performing source distribution, a complete 150 | machine-readable copy of the corresponding source code, to be 151 | distributed under the terms of Sections 1 and 2 above on a medium 152 | customarily used for software interchange; or, 153 | 154 | c) Accompany it with the information you received as to the offer 155 | to distribute corresponding source code. (This alternative is 156 | allowed only for noncommercial distribution and only if you 157 | received the program in object code or executable form with such 158 | an offer, in accord with Subsection b above.) 159 | 160 | The source code for a work means the preferred form of the work for 161 | making modifications to it. For an executable work, complete source 162 | code means all the source code for all modules it contains, plus any 163 | associated interface definition files, plus the scripts used to 164 | control compilation and installation of the executable. However, as a 165 | special exception, the source code distributed need not include 166 | anything that is normally distributed (in either source or binary 167 | form) with the major components (compiler, kernel, and so on) of the 168 | operating system on which the executable runs, unless that component 169 | itself accompanies the executable. 170 | 171 | If distribution of executable or object code is made by offering 172 | access to copy from a designated place, then offering equivalent 173 | access to copy the source code from the same place counts as 174 | distribution of the source code, even though third parties are not 175 | compelled to copy the source along with the object code. 176 | 177 | 4. You may not copy, modify, sublicense, or distribute the Program 178 | except as expressly provided under this License. Any attempt 179 | otherwise to copy, modify, sublicense or distribute the Program is 180 | void, and will automatically terminate your rights under this License. 181 | However, parties who have received copies, or rights, from you under 182 | this License will not have their licenses terminated so long as such 183 | parties remain in full compliance. 184 | 185 | 5. You are not required to accept this License, since you have not 186 | signed it. However, nothing else grants you permission to modify or 187 | distribute the Program or its derivative works. These actions are 188 | prohibited by law if you do not accept this License. Therefore, by 189 | modifying or distributing the Program (or any work based on the 190 | Program), you indicate your acceptance of this License to do so, and 191 | all its terms and conditions for copying, distributing or modifying 192 | the Program or works based on it. 193 | 194 | 6. Each time you redistribute the Program (or any work based on the 195 | Program), the recipient automatically receives a license from the 196 | original licensor to copy, distribute or modify the Program subject to 197 | these terms and conditions. You may not impose any further 198 | restrictions on the recipients' exercise of the rights granted herein. 199 | You are not responsible for enforcing compliance by third parties to 200 | this License. 201 | 202 | 7. If, as a consequence of a court judgment or allegation of patent 203 | infringement or for any other reason (not limited to patent issues), 204 | conditions are imposed on you (whether by court order, agreement or 205 | otherwise) that contradict the conditions of this License, they do not 206 | excuse you from the conditions of this License. If you cannot 207 | distribute so as to satisfy simultaneously your obligations under this 208 | License and any other pertinent obligations, then as a consequence you 209 | may not distribute the Program at all. For example, if a patent 210 | license would not permit royalty-free redistribution of the Program by 211 | all those who receive copies directly or indirectly through you, then 212 | the only way you could satisfy both it and this License would be to 213 | refrain entirely from distribution of the Program. 214 | 215 | If any portion of this section is held invalid or unenforceable under 216 | any particular circumstance, the balance of the section is intended to 217 | apply and the section as a whole is intended to apply in other 218 | circumstances. 219 | 220 | It is not the purpose of this section to induce you to infringe any 221 | patents or other property right claims or to contest validity of any 222 | such claims; this section has the sole purpose of protecting the 223 | integrity of the free software distribution system, which is 224 | implemented by public license practices. Many people have made 225 | generous contributions to the wide range of software distributed 226 | through that system in reliance on consistent application of that 227 | system; it is up to the author/donor to decide if he or she is willing 228 | to distribute software through any other system and a licensee cannot 229 | impose that choice. 230 | 231 | This section is intended to make thoroughly clear what is believed to 232 | be a consequence of the rest of this License. 233 | 234 | 8. If the distribution and/or use of the Program is restricted in 235 | certain countries either by patents or by copyrighted interfaces, the 236 | original copyright holder who places the Program under this License 237 | may add an explicit geographical distribution limitation excluding 238 | those countries, so that distribution is permitted only in or among 239 | countries not thus excluded. In such case, this License incorporates 240 | the limitation as if written in the body of this License. 241 | 242 | 9. The Free Software Foundation may publish revised and/or new versions 243 | of the General Public License from time to time. Such new versions will 244 | be similar in spirit to the present version, but may differ in detail to 245 | address new problems or concerns. 246 | 247 | Each version is given a distinguishing version number. If the Program 248 | specifies a version number of this License which applies to it and "any 249 | later version", you have the option of following the terms and conditions 250 | either of that version or of any later version published by the Free 251 | Software Foundation. If the Program does not specify a version number of 252 | this License, you may choose any version ever published by the Free Software 253 | Foundation. 254 | 255 | 10. If you wish to incorporate parts of the Program into other free 256 | programs whose distribution conditions are different, write to the author 257 | to ask for permission. For software which is copyrighted by the Free 258 | Software Foundation, write to the Free Software Foundation; we sometimes 259 | make exceptions for this. Our decision will be guided by the two goals 260 | of preserving the free status of all derivatives of our free software and 261 | of promoting the sharing and reuse of software generally. 262 | 263 | NO WARRANTY 264 | ----------- 265 | 266 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 267 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 268 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 269 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 270 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 271 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 272 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 273 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 274 | REPAIR OR CORRECTION. 275 | 276 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 277 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 278 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 279 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 280 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 281 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 282 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 283 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 284 | POSSIBILITY OF SUCH DAMAGES. 285 | 286 | --------------------------- 287 | END OF TERMS AND CONDITIONS 288 | -------------------------------------------------------------------------------- /core/components/tinymcerte/docs/readme.md: -------------------------------------------------------------------------------- 1 | # TinyMCE Rich Text Editor 2 | 3 | TinyMCE 6 for MODX Revolution. 4 | 5 | - Author: John Peca 6 | - Upgrade to TinyMCE 5: Thomas Jakobi 7 | - Upgrade to TinyMCE 6: Mat Dave Jones 8 | - License: GNU GPLv2 9 | 10 | ## Features 11 | 12 | TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor. It 13 | allows non-technical users to format content without knowing how to code. This 14 | release was done as a companion project for the https://a11y.modx.com to provide 15 | an accessible RTE. It is based on the TinyMCE 5 code base. 16 | 17 | ## Installation 18 | 19 | MODX Package Management 20 | 21 | ## Usage 22 | 23 | Install via package manager. 24 | 25 | ## GitHub Repository 26 | 27 | https://github.com/modxcms/tinymce-rte 28 | -------------------------------------------------------------------------------- /core/components/tinymcerte/elements/plugins/tinymcerte.plugin.php: -------------------------------------------------------------------------------- 1 | event->name; 13 | 14 | $corePath = $modx->getOption('tinymcerte.core_path', null, $modx->getOption('core_path') . 'components/tinymcerte/'); 15 | /** @var TinyMCERTE $tinymcerte */ 16 | $tinymcerte = $modx->getService('tinymcerte', 'TinyMCERTE', $corePath . 'model/tinymcerte/', [ 17 | 'core_path' => $corePath 18 | ]); 19 | 20 | if ($tinymcerte) { 21 | if (class_exists($className)) { 22 | $handler = new $className($modx, $scriptProperties); 23 | if (get_class($handler) == $className) { 24 | $handler->run(); 25 | } else { 26 | $modx->log(xPDO::LOG_LEVEL_ERROR, $className. ' could not be initialized!', '', 'TinyMCE RTE Plugin'); 27 | } 28 | } else { 29 | $modx->log(xPDO::LOG_LEVEL_ERROR, $className. ' was not found!', '', 'TinyMCE RTE Plugin'); 30 | } 31 | } 32 | 33 | return; 34 | -------------------------------------------------------------------------------- /core/components/tinymcerte/lexicon/de/default.inc.php: -------------------------------------------------------------------------------- 1 | https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 14 | $_lang['setting_tinymcerte.blocks_format'] = 'Block-Formate'; 15 | $_lang['setting_tinymcerte.blocks_format_desc'] = 'Mit dieser Einstellung können Sie erweiterte Stil-Formate für Blöcke zum Editor hinzufügen. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 16 | $_lang['setting_tinymcerte.browser_spellcheck'] = 'Rechtschreibprüfung'; 17 | $_lang['setting_tinymcerte.browser_spellcheck_desc'] = 'Browser-Rechtschreibprüfung aktivieren oder deaktivieren. https://www.tiny.cloud/docs/configure/spelling/#browser_spellcheck'; 18 | $_lang['setting_tinymcerte.content_css'] = 'Inhalts-CSS'; 19 | $_lang['setting_tinymcerte.content_css_desc'] = 'Zusätzliche CSS-Dateien mit TinyMCE laden. Verwenden Sie "," (Komma) als Trennzeichen für mehrere Dateien. https://www.tiny.cloud/docs/configure/content-appearance/#content_css'; 20 | $_lang['setting_tinymcerte.enable_link_list'] = 'Linkliste aktivieren'; 21 | $_lang['setting_tinymcerte.enable_link_list_desc'] = 'Diese Option aktiviert die verschachtelte Linkliste im Link-Plugin. Sie kann ausgeschaltet werden, wenn die Linkliste auf sehr großen Seiten einen Timeout erzeugt.'; 22 | $_lang['setting_tinymcerte.external_config'] = 'Externe Konfiguration'; 23 | $_lang['setting_tinymcerte.external_config_desc'] = 'Pfad zur externen Konfigurationsdatei, die mit den Vorgaben aus der Systemeinstellung zusammengeführt wird. Die Konfigurationsdatei muss ein gültiges JSON-Objekt enthalten.'; 24 | $_lang['setting_tinymcerte.headers_format'] = 'Titel-Formate'; 25 | $_lang['setting_tinymcerte.headers_format_desc'] = 'Mit dieser Einstellung können Sie erweiterte Stil-Formate für Titel-Formate zum Editor hinzufügen. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 26 | $_lang['setting_tinymcerte.image_advtab'] = 'Erweiterte Registerkarte für Bilder'; 27 | $_lang['setting_tinymcerte.image_advtab_desc'] = 'Mit dieser Einstellung können Sie die erweiterten Optionen im Bild-Dialog aktivieren, die es Ihnen ermöglichen, benutzerdefinierte Stile zu Bildern hinzuzufügen. https://www.tiny.cloud/docs/plugins/opensource/image/#image_advtab'; 28 | $_lang['setting_tinymcerte.image_class_list'] = 'Klassenliste für Bilder'; 29 | $_lang['setting_tinymcerte.image_class_list_desc'] = 'Klassenliste für Bilder. https://www.tiny.cloud/docs/plugins/opensource/image/#image_class_list'; 30 | $_lang['setting_tinymcerte.inline_format'] = 'Inline-Formate'; 31 | $_lang['setting_tinymcerte.inline_format_desc'] = 'Mit dieser Einstellung können Sie erweiterte Stil-Formate für Inline-Elemente zum Editor hinzufügen. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 32 | $_lang['setting_tinymcerte.link_class_list'] = 'Klassenliste für Links'; 33 | $_lang['setting_tinymcerte.link_class_list_desc'] = 'Klassenliste für Links. https://www.tiny.cloud/docs/plugins/opensource/link/#link_class_list'; 34 | $_lang['setting_tinymcerte.links_across_contexts'] = 'Kontextübergreifende Link-Liste'; 35 | $_lang['setting_tinymcerte.links_across_contexts_desc'] = 'Wenn die Einstellung aktiv ist, listet der Editor in der Link-Liste Ressourcen aus anderen Kontexten auf. Ansonsten werden nur Ressourcen aus demselben Kontext angezeigt.'; 36 | $_lang['setting_tinymcerte.max_height'] = 'Maximale Höhe'; 37 | $_lang['setting_tinymcerte.max_height_desc'] = 'Die Option max_height hat zwei Verhaltensweisen, abhängig vom Status des Plugins autoresize. Ohne das autoresize-Plugin können Sie mit dieser Option die maximale Höhe festlegen, mit der ein Benutzer die gesamte TinyMCE-Oberfläche strecken kann (indem er den verschiebbaren Bereich unten rechts in der Editor-Oberfläche anfasst). Mit dem Autoresize-Plugin legt diese Option die maximale Höhe fest, auf die sich der Editor automatisch ausdehnen kann.'; 38 | $_lang['setting_tinymcerte.menubar'] = 'Menüleiste'; 39 | $_lang['setting_tinymcerte.menubar_desc'] = 'Mit dieser Einstellung können Sie die Hauptmenüpunkte konfigurieren, die in der Menüleiste erscheinen sollen. https://www.tiny.cloud/docs/configure/editor-appearance/#menubar'; 40 | $_lang['setting_tinymcerte.modai.global.text.base_prompt'] = 'Base Prompt'; 41 | $_lang['setting_tinymcerte.modai.global.text.base_prompt_desc'] = 'Dies ist ein allgemeiner Anweisungsmodifikator, der standardmäßig zu jeder API-Anfrage hinzugefügt wird, ähnlich dem, den Sie für Customize ChatGPT auf deren Website eingeben würden.'; 42 | $_lang['setting_tinymcerte.modai.global.text.modify_prompts'] = 'Modify Prompts'; 43 | $_lang['setting_tinymcerte.modai.global.text.modify_prompts_desc'] = 'Dies ist eine JSON-Konfiguration, die Prompts für jede Kategorie und Menüoption definiert. Sie können Sprachen, Stile, Töne oder andere Optionen hinzufügen oder ändern.'; 44 | $_lang['setting_tinymcerte.object_resizing'] = 'Größenänderung für Objekte'; 45 | $_lang['setting_tinymcerte.object_resizing_desc'] = 'Mit dieser Einstellung können Sie die Griffe zur manuellen Grössenanpassung von Bildern, Tabellen oder Medienobjekten ein-/ausschalten. https://www.tiny.cloud/docs/configure/advanced-editing-behavior/#object_resizing'; 46 | $_lang['setting_tinymcerte.paste_as_text'] = 'Als Text einfügen'; 47 | $_lang['setting_tinymcerte.paste_as_text_desc'] = 'Mit dieser Einstellung können Sie den Standardzustand des ‚Einfügen als Text‘ Menüpunktes im Bearbeiten-Menü festlegen. https://www.tiny.cloud/docs/plugins/opensource/paste/#paste_as_text'; 48 | $_lang['setting_tinymcerte.plugins'] = 'Plugins'; 49 | $_lang['setting_tinymcerte.plugins_desc'] = 'Legen Sie fest, welche Plugins geladen werden (Standardmäßig werden keine geladen.) https://www.tiny.cloud/docs/configure/integration-and-setup/#plugins'; 50 | $_lang['setting_tinymcerte.relative_urls'] = 'Relative URLs'; 51 | $_lang['setting_tinymcerte.relative_urls_desc'] = 'Wenn diese Einstellung aktiviert ist, werden alle vom MODX Datei Browser zurückgegebenen URLs relativ zur angegebenen document_base_url gesetzt. Wenn sie deaktiviert ist, werden alle URLs in absolute URLs umgewandelt.'; 52 | $_lang['setting_tinymcerte.remove_script_host'] = 'Script-Host entfernen'; 53 | $_lang['setting_tinymcerte.remove_script_host_desc'] = 'Wenn diese Einstellung aktiviert ist, wird der Protokoll- und Hostteil von URLs entfernt, die vom MODX Datei Browser zurückgegeben werden. Diese (Option wird nur verwendet, wenn die Option relative_urls auf false gesetzt ist.)'; 54 | $_lang['setting_tinymcerte.settings'] = 'Zusätzliche Einstellungen'; 55 | $_lang['setting_tinymcerte.settings_desc'] = 'JSON-kodiertes Objekt mit zusätzlichen TinyMCE-Einstellungen.'; 56 | $_lang['setting_tinymcerte.skin'] = 'Erscheinungsbild'; 57 | $_lang['setting_tinymcerte.skin_desc'] = 'Mit dieser Einstellung können Sie das Erscheinungsbild festlegen, das TinyMCE verwenden soll. https://www.tiny.cloud/docs/configure/editor-appearance/#skin'; 58 | $_lang['setting_tinymcerte.statusbar'] = 'Statusleiste'; 59 | $_lang['setting_tinymcerte.statusbar_desc'] = 'Mit dieser Einstellung können Sie festlegen, ob TinyMCE die Statusleiste am unteren Rand des Editors anzeigen soll. https://www.tiny.cloud/docs/configure/editor-appearance/#statusbar'; 60 | $_lang['setting_tinymcerte.style_formats'] = 'Stil-Formate'; 61 | $_lang['setting_tinymcerte.style_formats_desc'] = 'Mit dieser Einstellung können Sie dem Editor erweiterte Stilformate für Text und andere Elemente hinzufügen. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 62 | $_lang['setting_tinymcerte.style_formats_merge'] = 'Stil-Formate zusammenführen'; 63 | $_lang['setting_tinymcerte.style_formats_merge_desc'] = 'Wenn Sie Ihre Stil-Formate mit den vorgegebenen Inhalt in styles_format zusammenführen möchten. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 64 | $_lang['setting_tinymcerte.toolbar1'] = 'Werkzeugleiste 1'; 65 | $_lang['setting_tinymcerte.toolbar1_desc'] = 'Dies steuert, welche Schaltflächen in der Werkzeugleiste 1 angezeigt werden sollen. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 66 | $_lang['setting_tinymcerte.toolbar2'] = 'Werkzeugleiste 2'; 67 | $_lang['setting_tinymcerte.toolbar2_desc'] = 'Dies steuert, welche Schaltflächen in der Werkzeugleiste 2 angezeigt werden sollen. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 68 | $_lang['setting_tinymcerte.toolbar3'] = 'Werkzeugleiste 3'; 69 | $_lang['setting_tinymcerte.toolbar3_desc'] = 'Dies steuert, welche Schaltflächen in der Werkzeugleiste 3 angezeigt werden sollen. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 70 | $_lang['setting_tinymcerte.valid_elements'] = 'Gültige Elemente'; 71 | $_lang['setting_tinymcerte.valid_elements_desc'] = 'Diese Einstellung legt fest, welche Elemente im bearbeiteten Text verbleiben, wenn der Editor speichert. Sie können dies verwenden, um das zurückgegebene HTML auf eine Teilmenge zu beschränken. https://www.tiny.cloud/docs/configure/content-filtering/#valid_elements.'; 72 | -------------------------------------------------------------------------------- /core/components/tinymcerte/lexicon/en/default.inc.php: -------------------------------------------------------------------------------- 1 | https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 14 | $_lang['setting_tinymcerte.blocks_format'] = 'Blocks format'; 15 | $_lang['setting_tinymcerte.blocks_format_desc'] = 'This option enables you to add more advanced style formats for text and other elements to the editor. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 16 | $_lang['setting_tinymcerte.browser_spellcheck'] = 'Spellcheck'; 17 | $_lang['setting_tinymcerte.browser_spellcheck_desc'] = 'Enable or disable browser spellscheck. https://www.tiny.cloud/docs/configure/spelling/#browser_spellcheck'; 18 | $_lang['setting_tinymcerte.content_css'] = 'Content CSS'; 19 | $_lang['setting_tinymcerte.content_css_desc'] = 'Load additional CSS files with TinyMCE. Use "," (comma) delimiter for multiple files. https://www.tiny.cloud/docs/configure/content-appearance/#content_css'; 20 | $_lang['setting_tinymcerte.enable_link_list'] = 'Enable link list'; 21 | $_lang['setting_tinymcerte.enable_link_list_desc'] = 'This option enables the nested link list in the link plugin. It can be switched off, when the link list creates a timeout on very large sites.'; 22 | $_lang['setting_tinymcerte.external_config'] = 'External config'; 23 | $_lang['setting_tinymcerte.external_config_desc'] = 'Path to an external JSON config file. It will be merged with with the system setting defaults.'; 24 | $_lang['setting_tinymcerte.headers_format'] = 'Headers format'; 25 | $_lang['setting_tinymcerte.headers_format_desc'] = 'This option enables you to add more advanced style formats for text and other elements to the editor. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 26 | $_lang['setting_tinymcerte.image_advtab'] = 'Advanced tab for images'; 27 | $_lang['setting_tinymcerte.image_advtab_desc'] = 'This option enables the advanced tab the image dialog allowing you to add custom styles to images. https://www.tiny.cloud/docs/plugins/opensource/image/#image_advtab'; 28 | $_lang['setting_tinymcerte.image_class_list'] = 'Image class list'; 29 | $_lang['setting_tinymcerte.image_class_list_desc'] = 'Class list for images. https://www.tiny.cloud/docs/plugins/opensource/image/#image_class_list'; 30 | $_lang['setting_tinymcerte.inline_format'] = 'Inline format'; 31 | $_lang['setting_tinymcerte.inline_format_desc'] = 'This option enables you to add more advanced style formats for text and other elements to the editor. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 32 | $_lang['setting_tinymcerte.link_class_list'] = 'Link class list'; 33 | $_lang['setting_tinymcerte.link_class_list_desc'] = 'Class list for links. https://www.tiny.cloud/docs/plugins/opensource/link/#link_class_list'; 34 | $_lang['setting_tinymcerte.links_across_contexts'] = 'Links across contexts'; 35 | $_lang['setting_tinymcerte.links_across_contexts_desc'] = 'Lists resources from other contexts when creating internal links in the editor. Otherwise only Resources from the same context appear.'; 36 | $_lang['setting_tinymcerte.max_height'] = 'Max height'; 37 | $_lang['setting_tinymcerte.max_height_desc'] = 'The max_height option has two kinds of behaviors depending on the state of the autoresize plugin. Without the autoresize plugin, this option allows you to set the maximum height that a user can stretch the entire TinyMCE interface (by grabbing the draggable area in the bottom right of the editor interface). With the autoresize plugin, this option sets the maximum height the editor can automatically expand to.'; 38 | $_lang['setting_tinymcerte.min_height'] = 'Min height'; 39 | $_lang['setting_tinymcerte.min_height_desc'] = 'The min_height option has two kinds of behaviors depending on the state of the autoresize plugin. Without the autoresize plugin, this option allows you to set the minimum height that a user can shrink the entire TinyMCE interface (by grabbing the draggable area in the bottom right of the editor interface). With the autoresize plugin, this option sets the minimum height the editor can automatically shrink to.'; 40 | $_lang['setting_tinymcerte.menubar'] = 'Menubar'; 41 | $_lang['setting_tinymcerte.menubar_desc'] = 'This option allows you to configure the menus you want to appear in the menu bar. https://www.tiny.cloud/docs/configure/editor-appearance/#menubar'; 42 | $_lang['setting_tinymcerte.modai.global.text.base_prompt'] = 'Base Prompt'; 43 | $_lang['setting_tinymcerte.modai.global.text.base_prompt_desc'] = 'This is an overall instruction modifier that is added to each API request by default, similar to what you would enter for Customize ChatGPT on their website.'; 44 | $_lang['setting_tinymcerte.modai.global.text.modify_prompts'] = 'Modify Prompts'; 45 | $_lang['setting_tinymcerte.modai.global.text.modify_prompts_desc'] = 'This is a JSON configuration that defines prompts for each category and menu option. Feel free to add or change languages, styles, tones, or any other options.'; 46 | $_lang['setting_tinymcerte.object_resizing'] = 'Object resizing'; 47 | $_lang['setting_tinymcerte.object_resizing_desc'] = 'This options allows you to turn on/off the resizing handles on images, tables or media objects. https://www.tiny.cloud/docs/configure/advanced-editing-behavior/#object_resizing'; 48 | $_lang['setting_tinymcerte.paste_as_text'] = 'Paste as text'; 49 | $_lang['setting_tinymcerte.paste_as_text_desc'] = 'This option enables you to set the default state of the "paste as text" edit menu option. https://www.tiny.cloud/docs/plugins/opensource/paste/#paste_as_text'; 50 | $_lang['setting_tinymcerte.plugins'] = 'Plugins'; 51 | $_lang['setting_tinymcerte.plugins_desc'] = 'Set which plugins are loaded (None are loaded by default). https://www.tiny.cloud/docs/configure/integration-and-setup/#plugins'; 52 | $_lang['setting_tinymcerte.relative_urls'] = 'Relative URLs'; 53 | $_lang['setting_tinymcerte.relative_urls_desc'] = 'If this option is set to true, all URLs starting with the URL in the MODX site_url system setting will be converted to a relative URL to this site_url. If it’s set to false all URLs will be converted to absolute URLs (see remove_script_host for additional options in that case).'; 54 | $_lang['setting_tinymcerte.remove_script_host'] = 'Remove script host'; 55 | $_lang['setting_tinymcerte.remove_script_host_desc'] = 'Removes the protocol and host part of URLs returned from the MODX file browser (Only used if relative_urls is off).'; 56 | $_lang['setting_tinymcerte.settings'] = 'Additional settings'; 57 | $_lang['setting_tinymcerte.settings_desc'] = 'JSON-encoded object of additional TinyMCE settings.'; 58 | $_lang['setting_tinymcerte.skin'] = 'Skin'; 59 | $_lang['setting_tinymcerte.skin_desc'] = 'This option allows you to specify the skin that TinyMCE should use. https://www.tiny.cloud/docs/configure/editor-appearance/#skin'; 60 | $_lang['setting_tinymcerte.statusbar'] = 'Statusbar'; 61 | $_lang['setting_tinymcerte.statusbar_desc'] = 'This option allows you to specify whether TinyMCE should display the status bar at the bottom of the editor. https://www.tiny.cloud/docs/configure/editor-appearance/#statusbar'; 62 | $_lang['setting_tinymcerte.style_formats'] = 'Style formats'; 63 | $_lang['setting_tinymcerte.style_formats_desc'] = 'This option enables you to add more advanced style formats for text and other elements to the editor. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 64 | $_lang['setting_tinymcerte.style_formats_merge'] = 'Merge style formats'; 65 | $_lang['setting_tinymcerte.style_formats_merge_desc'] = 'If you want to merge your styles to the default styles_format. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 66 | $_lang['setting_tinymcerte.toolbar1'] = 'Toolbar 1'; 67 | $_lang['setting_tinymcerte.toolbar1_desc'] = 'This controls what buttons you want show up in the toolbar. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 68 | $_lang['setting_tinymcerte.toolbar2'] = 'Toolbar 2'; 69 | $_lang['setting_tinymcerte.toolbar2_desc'] = 'This controls what buttons you want show up in the toolbar. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 70 | $_lang['setting_tinymcerte.toolbar3'] = 'Toolbar 3'; 71 | $_lang['setting_tinymcerte.toolbar3_desc'] = 'This controls what buttons you want show up in the toolbar. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 72 | $_lang['setting_tinymcerte.valid_elements'] = 'Valid elements'; 73 | $_lang['setting_tinymcerte.valid_elements_desc'] = 'This option defines which elements will remain in the edited text when the editor saves. You can use this to limit the returned HTML to a subset. https://www.tiny.cloud/docs/configure/content-filtering/#valid_elements.'; 74 | $_lang['setting_tinymcerte.selection_toolbar'] = 'Selection Toolbar'; 75 | $_lang['setting_tinymcerte.selection_toolbar_desc'] = 'This option defines what is shown in the toolbar when selecting text. https://www.tiny.cloud/docs/tinymce/latest/quickbars/#quickbars_selection_toolbar.'; 76 | $_lang['setting_tinymcerte.insert_toolbar'] = 'Insert Toolbar'; 77 | $_lang['setting_tinymcerte.insert_toolbar_desc'] = 'This option defines what is shown in the toolbar when the cursor is at the start of an empty line. https://www.tiny.cloud/docs/tinymce/latest/quickbars/#quickbars_insert_toolbar.'; 78 | $_lang['setting_tinymcerte.tiny_url'] = 'TinyMCE URL'; 79 | $_lang['setting_tinymcerte.tiny_url_desc'] = 'The URL or path to the TinyMCE JavaScript file.'; 80 | $_lang['setting_tinymcerte.lit'] = 'Last Install Time'; 81 | $_lang['setting_tinymcerte.lit_desc'] = 'Last package install in unix time for cache busting.'; 82 | -------------------------------------------------------------------------------- /core/components/tinymcerte/lexicon/it/default.inc.php: -------------------------------------------------------------------------------- 1 | https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 14 | $_lang['setting_tinymcerte.blocks_format'] = 'Formato blocchi'; 15 | $_lang['setting_tinymcerte.blocks_format_desc'] = 'Questa opzione consente di aggiungere formati di stile più avanzati per il testo e altri elementi all’editor. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 16 | $_lang['setting_tinymcerte.browser_spellcheck'] = 'Controllo ortografico'; 17 | $_lang['setting_tinymcerte.browser_spellcheck_desc'] = 'Abilita o disabilita il controllo ortografico del browser. https://www.tiny.cloud/docs/configure/spelling/#browser_spellcheck'; 18 | $_lang['setting_tinymcerte.content_css'] = 'CSS contenuto'; 19 | $_lang['setting_tinymcerte.content_css_desc'] = 'Carica file CSS aggiuntivi con TinyMCE. Usa il delimitatore "," (comma) per più file. https://www.tiny.cloud/docs/configure/content-appearance/#content_css'; 20 | $_lang['setting_tinymcerte.enable_link_list'] = 'Abilita elenco link'; 21 | $_lang['setting_tinymcerte.enable_link_list_desc'] = 'Questa opzione abilita l’elenco dei link annidati nel plugin dei link. Può essere disattivata, quando l’elenco dei link crea un timeout su siti molto grandi.'; 22 | $_lang['setting_tinymcerte.external_config'] = 'Configurazione esterna'; 23 | $_lang['setting_tinymcerte.external_config_desc'] = 'Percorso a un file di configurazione JSON esterno. Sarà fuso con le impostazioni predefinite del sistema.'; 24 | $_lang['setting_tinymcerte.headers_format'] = 'Formato intestazioni'; 25 | $_lang['setting_tinymcerte.headers_format_desc'] = 'Questa opzione consente di aggiungere formati di stile più avanzati per il testo e altri elementi all’editor. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 26 | $_lang['setting_tinymcerte.image_advtab'] = 'Scheda avanzata immagine'; 27 | $_lang['setting_tinymcerte.image_advtab_desc'] = 'Questa opzione abilita la scheda avanzata della finestra immagine che permette di aggiungere stili personalizzati alle immagini. https://www.tiny.cloud/docs/plugins/opensource/image/#image_advtab'; 28 | $_lang['setting_tinymcerte.image_class_list'] = 'Lista classi immagine'; 29 | $_lang['setting_tinymcerte.image_class_list_desc'] = 'Elenco delle classi per le immagini. https://www.tiny.cloud/docs/plugins/opensource/image/#image_class_list'; 30 | $_lang['setting_tinymcerte.inline_format'] = 'Formato inline'; 31 | $_lang['setting_tinymcerte.inline_format_desc'] = 'Questa opzione consente di aggiungere formati di stile più avanzati per il testo e altri elementi all’editor. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 32 | $_lang['setting_tinymcerte.link_class_list'] = 'Lista classi collegamenti'; 33 | $_lang['setting_tinymcerte.link_class_list_desc'] = 'Elenco delle classi per i collegamenti. https://www.tiny.cloud/docs/plugins/opensource/link/#link_class_list'; 34 | $_lang['setting_tinymcerte.links_across_contexts'] = 'Collegamenti tra contesti'; 35 | $_lang['setting_tinymcerte.links_across_contexts_desc'] = 'Elenca le risorse di altri contesti quando si creano collegamenti interni nell’editor. Altrimenti appaiono solo le risorse dello stesso contesto.'; 36 | $_lang['setting_tinymcerte.max_height'] = 'Altezza massima'; 37 | $_lang['setting_tinymcerte.max_height_desc'] = 'L’opzione max_height ha due tipi di comportamento a seconda dello stato del plugin autoresize. Senza il plugin autoresize, questa opzione consente di impostare l’altezza massima che un utente può estendere all’intera interfaccia TinyMCE (afferrando l’area trascinabile in basso a destra dell’interfaccia dell’editor). Con il plugin autoresize, questa opzione imposta l’altezza massima a cui l’editor può espandersi automaticamente.'; 38 | $_lang['setting_tinymcerte.menubar'] = 'Barra dei menù'; 39 | $_lang['setting_tinymcerte.menubar_desc'] = 'Questa opzione ti permette di configurare i menu che vuoi apparire nella barra dei menu. https://www.tiny.cloud/docs/configure/editor-appearance/#menubar'; 40 | $_lang['setting_tinymcerte.modai.global.text.base_prompt'] = 'Prompt di base'; 41 | $_lang['setting_tinymcerte.modai.global.text.base_prompt_desc'] = 'Questo è un modificatore di istruzioni generale che viene aggiunto a ogni richiesta API per impostazione predefinita, simile a quello che si inserirebbe per personalizzare ChatGPT sul loro sito web'; 42 | $_lang['setting_tinymcerte.modai.global.text.modify_prompts'] = 'Modificare i prompt'; 43 | $_lang['setting_tinymcerte.modai.global.text.modify_prompts_desc'] = 'Questa è una configurazione JSON che definisce i prompt per ogni categoria e opzione di menu. Sentitevi liberi di aggiungere o modificare lingue, stili, toni o qualsiasi altra opzione.'; 44 | $_lang['setting_tinymcerte.object_resizing'] = 'Ridimensionamento oggetto'; 45 | $_lang['setting_tinymcerte.object_resizing_desc'] = 'Questa opzione consente di disattivare o disattivare le maniglie di ridimensionamento su immagini, tabelle o oggetti multimediali. https://www.tiny.cloud/docs/configure/advanced-editing-behavior/#object_resizing'; 46 | $_lang['setting_tinymcerte.paste_as_text'] = 'Incolla come testo'; 47 | $_lang['setting_tinymcerte.paste_as_text_desc'] = 'Questa opzione consente di impostare lo stato predefinito dell’opzione di modifica "incolla come testo". https://www.tiny.cloud/docs/plugins/opensource/paste/#paste_as_text'; 48 | $_lang['setting_tinymcerte.plugins'] = 'Plugin'; 49 | $_lang['setting_tinymcerte.plugins_desc'] = 'Imposta quali plugin sono caricati (Nessuno è caricato di default). https://www.tiny.cloud/docs/configure/integration-and-setup/#plugins'; 50 | $_lang['setting_tinymcerte.relative_urls'] = 'URL relativi'; 51 | $_lang['setting_tinymcerte.relative_urls_desc'] = 'Se questa opzione è impostata a VERO, tutti gli URL restituiti dal gestore di file MODX saranno relativi dal document_base_url specificato. Se impostato su falso, tutti gli URL saranno convertiti in URL assoluti.'; 52 | $_lang['setting_tinymcerte.remove_script_host'] = 'Rimuovi host script'; 53 | $_lang['setting_tinymcerte.remove_script_host_desc'] = 'Rimuove la parte di protocollo e di host degli URL restituiti dal gestore di file MODX (Usato solo se relative_urls è off).'; 54 | $_lang['setting_tinymcerte.settings'] = 'Impostazioni aggiuntive'; 55 | $_lang['setting_tinymcerte.settings_desc'] = 'Oggetto codificato JSON di ulteriori impostazioni TinyMCE.'; 56 | $_lang['setting_tinymcerte.skin'] = 'Aspetto'; 57 | $_lang['setting_tinymcerte.skin_desc'] = 'Questa opzione ti permette di specificare la skin che TinyMCE dovrebbe usare. https://www.tiny.cloud/docs/configure/editor-appearance/#skin'; 58 | $_lang['setting_tinymcerte.statusbar'] = 'Barra di stato'; 59 | $_lang['setting_tinymcerte.statusbar_desc'] = 'Questa opzione consente di specificare se TinyMCE debba visualizzare la barra di stato in fondo all’editor. https://www.tiny.cloud/docs/configure/editor-appearance/#statusbar'; 60 | $_lang['setting_tinymcerte.style_formats'] = 'Formati di stile'; 61 | $_lang['setting_tinymcerte.style_formats_desc'] = 'Questa opzione consente di aggiungere formati di stile più avanzati per il testo e altri elementi all’editor. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 62 | $_lang['setting_tinymcerte.style_formats_merge'] = 'Unione formati di stile'; 63 | $_lang['setting_tinymcerte.style_formats_merge_desc'] = 'Se vuoi unire i tuoi stili al formato predefinito styles_format. https://www.tiny.cloud/docs/configure/editor-appearance/#style_formats'; 64 | $_lang['setting_tinymcerte.toolbar1'] = 'Barra degli strumenti 1'; 65 | $_lang['setting_tinymcerte.toolbar1_desc'] = 'Questo controlla quali pulsanti vuoi visualizzare nella barra degli strumenti. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 66 | $_lang['setting_tinymcerte.toolbar2'] = 'Barra degli strumenti 2'; 67 | $_lang['setting_tinymcerte.toolbar2_desc'] = 'Questo controlla quali pulsanti vuoi visualizzare nella barra degli strumenti. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 68 | $_lang['setting_tinymcerte.toolbar3'] = 'Barra degli strumenti 3'; 69 | $_lang['setting_tinymcerte.toolbar3_desc'] = 'Questo controlla quali pulsanti vuoi visualizzare nella barra degli strumenti. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 70 | $_lang['setting_tinymcerte.valid_elements'] = 'Elementi validi'; 71 | $_lang['setting_tinymcerte.valid_elements_desc'] = 'Questa opzione definisce quali elementi rimarranno nel testo modificato quando l’ editor salva. È possibile utilizzare questo per limitare l’HTML restituito a un sottoset. https://www.tiny.cloud/docs/configure/content-filtering/#valid_elements.'; 72 | -------------------------------------------------------------------------------- /core/components/tinymcerte/lexicon/ru/default.inc.php: -------------------------------------------------------------------------------- 1 | https://www.tiny.cloud/docs-4x/configure/content-formatting/#style_formats'; 14 | $_lang['setting_tinymcerte.blocks_format'] = 'Формат блоков'; 15 | $_lang['setting_tinymcerte.blocks_format_desc'] = 'Этот параметр позволяет добавлять в редактор более продвинутые форматы стилей для текста и других элементов. https://www.tiny.cloud/docs-4x/configure/content-formatting/#style_formats'; 16 | $_lang['setting_tinymcerte.browser_spellcheck'] = 'Проверка орфографии'; 17 | $_lang['setting_tinymcerte.browser_spellcheck_desc'] = 'Включить или отключить проверку орфографии в браузере. https://www.tiny.cloud/docs-4x/configure/spelling/#browser_spellcheck'; 18 | $_lang['setting_tinymcerte.content_css'] = 'CSS для контента'; 19 | $_lang['setting_tinymcerte.content_css_desc'] = 'Загрузите дополнительные файлы CSS для TinyMCE. Используйте "," (запятую) разделитель для нескольких файлов. https://www.tiny.cloud/docs-4x/configure/content-appearance/#content_css'; 20 | $_lang['setting_tinymcerte.enable_link_list'] = 'Включить список ссылок'; 21 | $_lang['setting_tinymcerte.enable_link_list_desc'] = 'Эта опция включает вложенный список ссылок в плагине ссылок. Ее можно отключить, когда список ссылок создает таймаут на очень больших сайтах.'; 22 | $_lang['setting_tinymcerte.external_config'] = 'Внешний конфиг'; 23 | $_lang['setting_tinymcerte.external_config_desc'] = 'Путь к внешнему JSON-конфигурационному файлу. Он будет объединен с системными настройками по умолчанию.'; 24 | $_lang['setting_tinymcerte.headers_format'] = 'Формат заголовков'; 25 | $_lang['setting_tinymcerte.headers_format_desc'] = 'Этот параметр позволяет добавлять в редактор более продвинутые форматы стилей для текста и других элементов. https://www.tiny.cloud/docs-4x/configure/content-formatting/#style_formats'; 26 | $_lang['setting_tinymcerte.image_advtab'] = 'Расширенная вкладка для изображений'; 27 | $_lang['setting_tinymcerte.image_advtab_desc'] = 'Эта опция включает расширенную вкладку диалога изображений, позволяющую добавлять пользовательские стили к изображениям. https://www.tiny.cloud/docs-4x/plugins/image/#image_advtab'; 28 | $_lang['setting_tinymcerte.image_class_list'] = 'Список классов для изображений'; 29 | $_lang['setting_tinymcerte.image_class_list_desc'] = 'Список классов для изображений. https://www.tiny.cloud/docs-4x/plugins/image/#image_class_list'; 30 | $_lang['setting_tinymcerte.inline_format'] = 'Формат строчных элементов'; 31 | $_lang['setting_tinymcerte.inline_format_desc'] = 'Этот параметр позволяет добавлять в редактор более продвинутые форматы стилей для текста и других элементов. https://www.tiny.cloud/docs-4x/configure/content-formatting/#style_formats'; 32 | $_lang['setting_tinymcerte.link_class_list'] = 'Список классов для ссылок'; 33 | $_lang['setting_tinymcerte.link_class_list_desc'] = 'Список классов для изображений. https://www.tiny.cloud/docs-4x/plugins/image/#image_class_list'; 34 | $_lang['setting_tinymcerte.links_across_contexts'] = 'Ссылки в разных контекстах'; 35 | $_lang['setting_tinymcerte.links_across_contexts_desc'] = 'Списки ресурсов из других контекстов при создании внутренних ссылок в редакторе. В противном случае появляются только ресурсы из того же контекста.'; 36 | $_lang['setting_tinymcerte.max_height'] = 'Максимальная высота'; 37 | $_lang['setting_tinymcerte.max_height_desc'] = 'Опция max_height имеет два вида поведения в зависимости от состояния плагина autoresize. Без плагина autoresize этот параметр позволяет установить максимальную высоту, на которую пользователь может растянуть весь интерфейс TinyMCE (захватив перетаскиваемую область в правом нижнем углу интерфейса редактора). С плагином autoresize этот параметр устанавливает максимальную высоту, до которой редактор может автоматически расширяться.'; 38 | $_lang['setting_tinymcerte.menubar'] = 'Строка меню'; 39 | $_lang['setting_tinymcerte.menubar_desc'] = 'Это определяет, какие кнопки вы хотите показать на панели инструментов. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 40 | $_lang['setting_tinymcerte.modai.global.text.base_prompt'] = 'Base Prompt'; 41 | $_lang['setting_tinymcerte.modai.global.text.base_prompt_desc'] = 'Это общий модификатор инструкции, который добавляется к каждому API-запросу по умолчанию, подобно тому, что вы вводите для Customize ChatGPT на их сайте.'; 42 | $_lang['setting_tinymcerte.modai.global.text.modify_prompts'] = 'Изменить подсказки'; 43 | $_lang['setting_tinymcerte.modai.global.text.modify_prompts_desc'] = 'Это конфигурация JSON, определяющая подсказки для каждой категории и пункта меню. Не стесняйтесь добавлять или изменять языки, стили, тона или любые другие параметры.'; 44 | $_lang['setting_tinymcerte.object_resizing'] = 'Изменение размера объекта'; 45 | $_lang['setting_tinymcerte.object_resizing_desc'] = 'Эта опция позволяет включить/выключить обработку изменения размеров изображений, таблиц или медиа объектов. https://www.tiny.cloud/docs/configure/advanced-editing-behavior/#object_resizing'; 46 | $_lang['setting_tinymcerte.paste_as_text'] = 'Вставить как текст'; 47 | $_lang['setting_tinymcerte.paste_as_text_desc'] = 'Этот параметр позволяет установить состояние по умолчанию для пункта меню редактирования «вставить как текст». https://www.tiny.cloud/docs-4x/plugins/paste/#paste_as_text'; 48 | $_lang['setting_tinymcerte.plugins'] = 'Плагины'; 49 | $_lang['setting_tinymcerte.plugins_desc'] = 'Установите, какие плагины загружены (по умолчанию нет). https://www.tiny.cloud/docs/configure/integration-and-setup/#plugins'; 50 | $_lang['setting_tinymcerte.relative_urls'] = 'Относительные URL'; 51 | $_lang['setting_tinymcerte.relative_urls_desc'] = 'Если эта опция установлена в true, то все URL, возвращаемые из браузера MODX-файлов, будут относительными из указанного document_base_url. Если опция установлена в значение false, то все URL будут преобразованы в абсолютные.'; 52 | $_lang['setting_tinymcerte.remove_script_host'] = 'Удалить скрипт хоста'; 53 | $_lang['setting_tinymcerte.remove_script_host_desc'] = 'Удаляет протокол и хостовую часть URL-адресов, возвращаемых из браузера MODX-файлов (используется только в случае, если отключен относительный_урлс).'; 54 | $_lang['setting_tinymcerte.settings'] = 'Дополнительные настройки'; 55 | $_lang['setting_tinymcerte.settings_desc'] = 'Объект дополнительных настроек TinyMCE в кодировке JSON.'; 56 | $_lang['setting_tinymcerte.skin'] = 'Скин'; 57 | $_lang['setting_tinymcerte.skin_desc'] = 'Эта опция позволяет указать скин, который должен использовать TinyMCE. https://www.tiny.cloud/docs/configure/editor-appearance/#skin'; 58 | $_lang['setting_tinymcerte.statusbar'] = 'Статус бар'; 59 | $_lang['setting_tinymcerte.statusbar_desc'] = 'Эта опция позволяет указать, показывать ли TinyMCE строку состояния внизу редактора. https://www.tiny.cloud/docs/configure/editor-appearance/#statusbar'; 60 | $_lang['setting_tinymcerte.style_formats'] = 'Форматы стилей'; 61 | $_lang['setting_tinymcerte.style_formats_desc'] = 'Этот параметр позволяет добавлять в редактор более продвинутые форматы стилей для текста и других элементов. https://www.tiny.cloud/docs-4x/configure/content-formatting/#style_formats'; 62 | $_lang['setting_tinymcerte.style_formats_merge'] = 'Объединение форматов стилей'; 63 | $_lang['setting_tinymcerte.style_formats_merge_desc'] = 'Если вы хотите объединить ваши стили со стилем по умолчанию styles_format. https://www.tiny.cloud/docs-4x/configure/content-formatting/#style_formats'; 64 | $_lang['setting_tinymcerte.toolbar1'] = 'Панель инструментов 1'; 65 | $_lang['setting_tinymcerte.toolbar1_desc'] = 'Это определяет, какие кнопки вы хотите показать на панели инструментов. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 66 | $_lang['setting_tinymcerte.toolbar2'] = 'Панель инструментов 2'; 67 | $_lang['setting_tinymcerte.toolbar2_desc'] = 'Это определяет, какие кнопки вы хотите показать на панели инструментов. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 68 | $_lang['setting_tinymcerte.toolbar3'] = 'Панель инструментов 3'; 69 | $_lang['setting_tinymcerte.toolbar3_desc'] = 'Это определяет, какие кнопки вы хотите показать на панели инструментов. https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar'; 70 | $_lang['setting_tinymcerte.valid_elements'] = 'Допустимые элементы'; 71 | $_lang['setting_tinymcerte.valid_elements_desc'] = 'Эта опция определяет, какие элементы останутся в редактируемом тексте при сохранении редактора. Вы можете использовать это для ограничения возвращаемого HTML подмножества. https://www.tiny.cloud/docs/configure/content-filtering/#valid_elements.'; 72 | -------------------------------------------------------------------------------- /core/components/tinymcerte/model/tinymcerte/tinymcerte.class.php: -------------------------------------------------------------------------------- 1 | modx->getOption('use_editor', false); 23 | $whichEditor = $this->modx->getOption('which_editor', null, ''); 24 | 25 | if ($useEditor && $whichEditor == 'TinyMCE RTE') { 26 | // Load the tinymcerte lexicon, when the editor is TinyMCE RTE 27 | return parent::init(); 28 | } 29 | 30 | return false; 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | * @return void 36 | */ 37 | public function process() 38 | { 39 | $this->modx->controller->addLexiconTopic('tinymcerte:default'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/components/tinymcerte/src/Plugins/Events/OnRichTextBrowserInit.php: -------------------------------------------------------------------------------- 1 | modx->getOption('use_editor', false); 23 | $whichEditor = $this->modx->getOption('which_editor', null, ''); 24 | 25 | if ($useEditor && $whichEditor == 'TinyMCE RTE') { 26 | // Load the tinymcerte browser, when the editor is TinyMCE RTE 27 | return parent::init(); 28 | } 29 | 30 | return false; 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | * @return void 36 | */ 37 | public function process() 38 | { 39 | $this->modx->controller->addJavascript($this->tinymcerte->getOption('assetsUrl') . 'mgr/browser.min.js?v=' . $this->tinymcerte->version); 40 | $this->modx->event->output('TinyMCERTE.browserCallback'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/components/tinymcerte/src/Plugins/Events/OnRichTextEditorInit.php: -------------------------------------------------------------------------------- 1 | modx->getOption('use_editor', false); 25 | $whichEditor = $this->modx->getOption('which_editor', null, ''); 26 | 27 | if ($useEditor && $whichEditor == 'TinyMCE RTE') { 28 | // Load the tinymcerte scripts, when the editor is TinyMCE RTE 29 | return parent::init(); 30 | } 31 | 32 | return false; 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | * @return void 38 | */ 39 | public function process() 40 | { 41 | $html = $this->initTinyMCE(); 42 | 43 | $this->modx->event->output($html); 44 | } 45 | 46 | /** 47 | * Register TinyMCE scripts and return the configuration 48 | * 49 | * @return string 50 | */ 51 | private function initTinyMCE() 52 | { 53 | $tinyURL = $this->getTinyMCEURL(); 54 | $this->modx->controller->addJavascript($tinyURL . '?v=' . $this->tinymcerte->version); 55 | $this->modx->controller->addJavascript($this->tinymcerte->getOption('assetsUrl') . 'mgr/tinymcerte.min.js?v=' . $this->tinymcerte->version); 56 | $this->modx->controller->addCss($this->tinymcerte->getOption('assetsUrl') . 'mgr/tinymcerte.css?v=' . $this->tinymcerte->version); 57 | 58 | $configstring = json_encode($this->getTinyConfig(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); 59 | if (json_last_error()) { 60 | $this->modx->log(xPDO::LOG_LEVEL_ERROR, 'TinyMCE RTE configuration can\'t be encoded as JSON: ', json_last_error_msg()); 61 | } 62 | // unescape escaped regular expressions, that can't be contained directly in the external config 63 | $configstring = preg_replace_callback('/"##(.*?)##"/', function ($matches) { 64 | // replace double backslashes with single ones that have to be set for a valid json in the external config 65 | return str_replace(['\\\\'], ['\\'], $matches[1]); 66 | }, $configstring); 67 | 68 | $this->modx->controller->addHtml(''); 72 | 73 | return ''; 78 | } 79 | 80 | /** 81 | * Get the TinyMCE configuration 82 | * 83 | * @return array 84 | */ 85 | private function getTinyConfig() 86 | { 87 | switch ($this->tinymcerte->getOption('modxversion')) { 88 | case 3: 89 | $managerlanguage = $this->modx->getOption('cultureKey'); 90 | break; 91 | default: 92 | $managerlanguage = $this->modx->getOption('manager_language'); 93 | break; 94 | } 95 | $language = $this->tinymcerte->getLanguageCode($managerlanguage); 96 | $objectResizing = $this->tinymcerte->getOption('object_resizing', [], '1'); 97 | if ($objectResizing === '1' || $objectResizing === 'true') { 98 | $objectResizing = true; 99 | } elseif ($objectResizing === '0' || $objectResizing === 'false') { 100 | $objectResizing = false; 101 | } 102 | 103 | /** @var modResource $resource */ 104 | $resource = $this->modx->getOption('resource', $this->scriptProperties); 105 | if ($resource && $resource->get('context_key')) { 106 | $context = $this->modx->getContext($resource->get('context_key')); 107 | $documentBaseUrl = $context->getOption('site_url'); 108 | } else { 109 | $documentBaseUrl = $this->modx->getOption('site_url'); 110 | } 111 | 112 | $config = array_merge([ 113 | 'plugins' => $this->tinymcerte->getOption('plugins', [], 'advlist autoresize autolink lists charmap preview anchor visualblocks searchreplace code fullscreen insertdatetime media table image quickbars modxlink modai'), 114 | 'quickbars_insert_toolbar' => $this->tinymcerte->getOption('insert_toolbar', [], 'image media quicktable modxlink modai_generate'), 115 | 'quickbars_selection_toolbar' => $this->tinymcerte->getOption('selection_toolbar', [], 'bold italic underline | modxlink | modai_enhance'), 116 | 'toolbar1' => $this->tinymcerte->getOption('toolbar1', [], 'undo redo | styleselect | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | modxlink | image'), 117 | 'toolbar2' => $this->tinymcerte->getOption('toolbar2', [], ''), 118 | 'toolbar3' => $this->tinymcerte->getOption('toolbar3', [], ''), 119 | 'connector_url' => $this->tinymcerte->getOption('connectorUrl'), 120 | 'language' => $language, 121 | 'directionality' => $this->modx->getOption('manager_direction', [], 'ltr'), 122 | 'menubar' => $this->tinymcerte->getOption('menubar', [], 'file edit insert view format table tools'), 123 | 'statusbar' => $this->tinymcerte->getOption('statusbar', [], true) == 1, 124 | 'image_advtab' => $this->tinymcerte->getOption('image_advtab', [], true) == 1, 125 | 'paste_as_text' => $this->tinymcerte->getOption('paste_as_text', [], false) == 1, 126 | 'style_formats_merge' => $this->tinymcerte->getOption('style_formats_merge', [], false) == 1, 127 | 'object_resizing' => $objectResizing, 128 | 'link_class_list' => json_decode($this->tinymcerte->getOption('link_class_list', [], '[]'), true), 129 | 'browser_spellcheck' => $this->tinymcerte->getOption('browser_spellcheck', [], false) == 1, 130 | 'content_css' => $this->tinymcerte->explodeAndClean($this->tinymcerte->getOption('content_css', [], '')), 131 | 'image_class_list' => json_decode($this->tinymcerte->getOption('image_class_list', [], '[]'), true), 132 | 'skin' => $this->tinymcerte->getOption('skin', [], 'oxide'), 133 | 'relative_urls' => $this->tinymcerte->getOption('relative_urls', [], true) == 1, 134 | 'document_base_url' => $documentBaseUrl, 135 | 'remove_script_host' => $this->tinymcerte->getOption('remove_script_host', [], true) == 1, 136 | 'entity_encoding' => $this->tinymcerte->getOption('entity_encoding', [], 'named'), 137 | 'enable_link_list' => $this->tinymcerte->getOption('enable_link_list', [], true) == 1, 138 | 'max_height' => (int)$this->tinymcerte->getOption('max_height', [], 500), 139 | 'min_height' => (int)$this->tinymcerte->getOption('min_height', [], 100), 140 | 'branding' => $this->tinymcerte->getOption('branding', [], false) == 1, 141 | 'cache_suffix' => '?v=' . $this->tinymcerte->version, 142 | 'promotion' => false 143 | ], $this->getSettings(), $this->getProperties()); 144 | 145 | $styleFormats = $this->tinymcerte->getOption('style_formats', [], '[]'); 146 | $styleFormats = json_decode($styleFormats, true) ?? []; 147 | $finalFormats = []; 148 | foreach ($styleFormats as $format) { 149 | if (!isset($format['items'])) { 150 | continue; 151 | } elseif (is_array($format['items'])) { 152 | $items = $format['items']; 153 | } else { 154 | $items = $this->tinymcerte->getOption($format['items'], [], '[]'); 155 | $items = json_decode($items, true); 156 | } 157 | if (empty($items)) { 158 | continue; 159 | } 160 | $format['items'] = $items; 161 | $finalFormats[] = $format; 162 | } 163 | if (!empty($finalFormats)) { 164 | $config['style_formats'] = $finalFormats; 165 | } 166 | 167 | $validElements = $this->tinymcerte->getOption('valid_elements'); 168 | if (!empty($validElements)) { 169 | $config['valid_elements'] = $validElements; 170 | } 171 | 172 | $externalConfig = $this->tinymcerte->getOption('external_config'); 173 | if (!empty($externalConfig)) { 174 | $externalConfig = str_replace([ 175 | '{base_path}', 176 | '{core_path}', 177 | '{assets_path}', 178 | ], [ 179 | $this->modx->getOption('base_path'), 180 | $this->modx->getOption('core_path'), 181 | $this->modx->getOption('assets_path'), 182 | ], $externalConfig); 183 | if (file_exists($externalConfig) && is_readable($externalConfig)) { 184 | $externalConfig = file_get_contents($externalConfig); 185 | $externalConfig = json_decode($externalConfig, true); 186 | if (is_array($externalConfig)) { 187 | $config = array_replace_recursive($config, $externalConfig); 188 | } 189 | } 190 | } 191 | return $config; 192 | } 193 | 194 | /** 195 | * Get properties passed to OnRichTextEditorInit event, minus the ones set by the resource controllers 196 | * 197 | * @return array 198 | */ 199 | private function getProperties() 200 | { 201 | $props = $this->scriptProperties; 202 | // unset the regular properties sent by resource controllers 203 | unset($props['editor'], $props['elements'], $props['id'], $props['resource'], $props['mode']); 204 | foreach ($props as $key => $prop) { 205 | if (is_object($prop)) { 206 | unset($props[$key]); 207 | } 208 | } 209 | 210 | return $props; 211 | } 212 | 213 | /** 214 | * Get settings from a JSON encoded array in tinymcerte.settings system setting 215 | * 216 | * @return array 217 | */ 218 | private function getSettings() 219 | { 220 | $settings = json_decode($this->tinymcerte->getOption('settings'), true); 221 | return ($settings) ?: []; 222 | } 223 | 224 | private function getTinyMCEURL() 225 | { 226 | $tinyURL = $this->tinymcerte->getOption('tiny_url'); 227 | if (empty($tinyURL)) { 228 | $tinyURL = $this->modx->getOption('tinymcerte.tiny_url', null, $this->tinymcerte->getOption('assetsUrl') . 'mgr/tinymce/tinymce.min.js'); 229 | } 230 | if (strpos($tinyURL, '{') !== false) { 231 | $tinyURL = str_replace([ 232 | '{base_url}', 233 | '{assets_url}', 234 | '{tinymcerte.assets_url}', 235 | ], [ 236 | $this->modx->getOption('base_url'), 237 | $this->modx->getOption('assets_url'), 238 | $this->tinymcerte->getOption('assetsUrl'), 239 | ], $tinyURL); 240 | } 241 | return $tinyURL; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /core/components/tinymcerte/src/Plugins/Events/OnRichTextEditorRegister.php: -------------------------------------------------------------------------------- 1 | modx->event->output('TinyMCE RTE'); 23 | } 24 | } -------------------------------------------------------------------------------- /core/components/tinymcerte/src/Plugins/Plugin.php: -------------------------------------------------------------------------------- 1 | scriptProperties = &$scriptProperties; 33 | $this->modx = &$modx; 34 | $corePath = $this->modx->getOption('tinymcerte.core_path', null, $this->modx->getOption('core_path') . 'components/tinymcerte/'); 35 | $this->tinymcerte = $this->modx->getService('tinymcerte', 'TinyMCERTE', $corePath . 'model/tinymcerte/', [ 36 | 'core_path' => $corePath 37 | ]); 38 | } 39 | 40 | /** 41 | * Run the plugin event. 42 | */ 43 | public function run() 44 | { 45 | $init = $this->init(); 46 | if ($init !== true) { 47 | return; 48 | } 49 | 50 | $this->process(); 51 | } 52 | 53 | /** 54 | * Initialize the plugin event. 55 | * 56 | * @return bool 57 | */ 58 | public function init() 59 | { 60 | return true; 61 | } 62 | 63 | /** 64 | * Process the plugin event code. 65 | * 66 | * @return mixed 67 | */ 68 | abstract public function process(); 69 | } -------------------------------------------------------------------------------- /core/components/tinymcerte/src/Processors/GetTreeProcessor.php: -------------------------------------------------------------------------------- 1 | modx->getOption('tinymcerte.core_path', null, $this->modx->getOption('core_path') . 'components/tinymcerte/'); 41 | $this->tinymcerte = $this->modx->getService('tinymcerte', 'TinyMCERTE', $corePath . 'model/tinymcerte/'); 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | * @return bool 47 | */ 48 | public function checkPermissions() 49 | { 50 | return $this->modx->hasPermission('resource_tree'); 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | * @return string[] 56 | */ 57 | public function getLanguageTopics() 58 | { 59 | return ['tinymcerte:default']; 60 | } 61 | 62 | /** 63 | * {@inheritDoc} 64 | * @return string 65 | */ 66 | public function process() 67 | { 68 | $this->getRootNode(); 69 | $this->getResources($this->contextKey); 70 | if (!$this->tinymcerte->getOption('links_across_contexts')) { 71 | $items = $this->getContextTree($this->contextKey); 72 | } else { 73 | $items = []; 74 | $c = $this->modx->newQuery('modContext'); 75 | $c->where([ 76 | 'key:!=' => 'mgr' 77 | ]); 78 | $c->sortby($this->modx->escape('rank')); 79 | /** @var modContext[] $contexts */ 80 | $contexts = $this->modx->getCollection('modContext', $c); 81 | foreach ($contexts as $context) { 82 | $items[] = [ 83 | 'title' => $context->get('key'), 84 | 'menu' => $this->getContextTree($context->get('key')) 85 | ]; 86 | } 87 | } 88 | return $this->outputArray($items); 89 | } 90 | 91 | /** 92 | * Get the MODX tree for a context. 93 | * 94 | * @param $contextKey 95 | * @return array 96 | */ 97 | private function getContextTree($contextKey) 98 | { 99 | $tree = $this->modx->getTree($this->startNode, 10, [ 100 | 'context' => $contextKey 101 | ]); 102 | $resources = $this->getResources($contextKey); 103 | return $this->fillTree($tree, $resources); 104 | } 105 | 106 | /** 107 | * Get the MODX resources id and pagetitle in a context. 108 | * 109 | * @param string $context 110 | * @return array 111 | */ 112 | private function getResources($context) 113 | { 114 | $c = $this->modx->newQuery('modResource'); 115 | $c->where([ 116 | 'context_key' => $context, 117 | 'deleted' => false, 118 | ]); 119 | $c->select('id, pagetitle, published, hidemenu'); 120 | $result = []; 121 | if ($c->prepare() && $c->stmt->execute()) { 122 | $resoures = $c->stmt->fetchAll(PDO::FETCH_ASSOC); 123 | foreach ($resoures as $resource) { 124 | $classes = ($resource['published']) ? 'published ' : 'unpublished '; 125 | $classes .= ($resource['hidemenu']) ? 'hidden ' : 'visible '; 126 | $result[$resource['id']] = [ 127 | 'pagetitle' => $resource['pagetitle'], 128 | 'classes' => trim($classes) 129 | ]; 130 | } 131 | } 132 | 133 | return $result; 134 | } 135 | 136 | /** 137 | * Fill the tree recursive with the node/resource values. 138 | * 139 | * @param array $nodes 140 | * @param array $resources 141 | * @return array 142 | */ 143 | private function fillTree(array $nodes, array $resources) 144 | { 145 | $result = []; 146 | foreach ($nodes as $node => $subtree) { 147 | if (isset($resources[$node])) { 148 | if (is_array($subtree)) { 149 | $result[] = [ 150 | 'title' => $resources[$node]['pagetitle'] . ' (' . $node . ')', 151 | 'menu' => array_merge([ 152 | [ 153 | 'title' => '◁ ' . $resources[$node]['pagetitle'] . ' (' . $node . ')', 154 | 'value' => $node, 155 | 'display' => $resources[$node]['pagetitle'], 156 | 'classes' => $resources[$node]['classes'] 157 | ] 158 | ], 159 | $this->fillTree($subtree, $resources)) 160 | ]; 161 | } else { 162 | $result[] = [ 163 | 'title' => $resources[$node]['pagetitle'] . ' (' . $node . ')', 164 | 'value' => $node, 165 | 'display' => $resources[$node]['pagetitle'], 166 | 'classes' => $resources[$node]['classes'] 167 | ]; 168 | } 169 | } 170 | } 171 | return $result; 172 | } 173 | 174 | /** 175 | * Determine the context and root and start nodes for the tree. 176 | * 177 | * @return void 178 | */ 179 | public function getRootNode() 180 | { 181 | $ctx = $this->getProperty('wctx'); 182 | if ($ctx && $this->modx->switchContext($ctx)) { 183 | $this->contextKey = $ctx; 184 | $this->startNode = 0; 185 | } else { 186 | $this->contextKey = $this->modx->getOption('default_context'); 187 | $this->startNode = $this->modx->getOption('tree_root_id', null, 0); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /core/components/tinymcerte/src/Processors/SearchProcessor.php: -------------------------------------------------------------------------------- 1 | getProperty('id'); 24 | $ctx = $this->getProperty('wctx'); 25 | $query = $this->getProperty('query'); 26 | $limit = $this->getProperty('limit', 10); 27 | $crossContext = (bool) $this->modx->getOption('tinymcerte.links_across_contexts', null, false); 28 | $c = $this->modx->newQuery('modResource'); 29 | $c->where([ 30 | 'deleted' => false, 31 | ]); 32 | if (!empty($query)) { 33 | $c->where([ 34 | 'pagetitle:LIKE' => '%' . $query . '%' 35 | ]); 36 | } 37 | if (!empty($id)) { 38 | $c->where([ 39 | 'id:=' => $id, 40 | ]); 41 | } 42 | if (!empty($ctx) && !$crossContext) { 43 | $c->where([ 44 | 'context_key:=' => $ctx, 45 | ]); 46 | } 47 | $c->select('id, concat(pagetitle, " (", context_key, ")") as pagetitle, id as value'); 48 | $c->limit($limit); 49 | $results = []; 50 | if ($c->prepare() && $c->stmt->execute()) { 51 | $results = $c->stmt->fetchAll(PDO::FETCH_ASSOC); 52 | } 53 | return $this->outputArray($results); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/components/tinymcerte/src/TinyMCERTE.php: -------------------------------------------------------------------------------- 1 | modx =& $modx; 57 | $this->namespace = $this->getOption('namespace', $options, $this->namespace); 58 | 59 | $corePath = $this->getOption('core_path', $options, $this->modx->getOption('core_path', null, MODX_CORE_PATH) . 'components/' . $this->namespace . '/'); 60 | $assetsPath = $this->getOption('assets_path', $options, $this->modx->getOption('assets_path', null, MODX_ASSETS_PATH) . 'components/' . $this->namespace . '/'); 61 | $assetsUrl = $this->getOption('assets_url', $options, $this->modx->getOption('assets_url', null, MODX_ASSETS_URL) . 'components/' . $this->namespace . '/'); 62 | $modxversion = $this->modx->getVersionData(); 63 | $this->version = $this->getVersionData(); 64 | 65 | // Load some default paths for easier management 66 | $this->options = array_merge([ 67 | 'namespace' => $this->namespace, 68 | 'version' => $this->version, 69 | 'corePath' => $corePath, 70 | 'modelPath' => $corePath . 'model/', 71 | 'vendorPath' => $corePath . 'vendor/', 72 | 'chunksPath' => $corePath . 'elements/chunks/', 73 | 'snippetsPath' => $corePath . 'elements/snippets/', 74 | 'pluginsPath' => $corePath . 'elements/plugins/', 75 | 'controllersPath' => $corePath . 'controllers/', 76 | 'processorsPath' => $corePath . 'processors/', 77 | 'templatesPath' => $corePath . 'templates/', 78 | 'assetsPath' => $assetsPath, 79 | 'assetsUrl' => $assetsUrl, 80 | 'jsUrl' => $assetsUrl . 'js/', 81 | 'cssUrl' => $assetsUrl . 'css/', 82 | 'connectorUrl' => $assetsUrl . 'connector.php' 83 | ], $options); 84 | 85 | $lexicon = $this->modx->getService('lexicon', 'modLexicon'); 86 | $lexicon->load($this->namespace . ':default'); 87 | 88 | $this->packageName = $this->modx->lexicon('tinymcerte'); 89 | 90 | // Add default options 91 | $this->options = array_merge($this->options, [ 92 | 'debug' => (bool)$this->getOption('debug', $options, false), 93 | 'modxversion' => $modxversion['version'], 94 | ]); 95 | } 96 | 97 | /** 98 | * Get a local configuration option or a namespaced system setting by key. 99 | * 100 | * @param string $key The option key to search for. 101 | * @param array $options An array of options that override local options. 102 | * @param mixed $default The default value returned if the option is not found locally or as a 103 | * namespaced system setting; by default this value is null. 104 | * @return mixed The option value or the default value specified. 105 | */ 106 | public function getOption($key, $options = [], $default = null) 107 | { 108 | $option = $default; 109 | if (!empty($key) && is_string($key)) { 110 | if ($options != null && array_key_exists($key, $options)) { 111 | $option = $options[$key]; 112 | } elseif (array_key_exists($key, $this->options)) { 113 | $option = $this->options[$key]; 114 | } elseif (array_key_exists("$this->namespace.$key", $this->modx->config)) { 115 | $option = $this->modx->getOption("$this->namespace.$key"); 116 | } 117 | } 118 | return $option; 119 | } 120 | 121 | /** 122 | * Get the TinyMCE language code by the manager language 123 | * 124 | * @param $language 125 | * @return string 126 | */ 127 | public function getLanguageCode($language) 128 | { 129 | $codes = [ 130 | 'bg' => 'bg_BG', 131 | 'fr' => 'fr_FR', 132 | 'he' => 'he_IL', 133 | 'pt-br' => 'pt_BR', 134 | 'sv' => 'sv_SE', 135 | 'th' => 'th_TH', 136 | 'zh' => 'zh_CN', 137 | ]; 138 | 139 | if (isset($codes[$language])) { 140 | $language = $codes[$language]; 141 | } 142 | 143 | $langFile = $this->getOption('assetsPath') . 'js/vendor/tinymce/langs/' . $language . '.js'; 144 | if (!file_exists(($langFile))) { 145 | return 'en'; 146 | } 147 | 148 | return $language; 149 | } 150 | 151 | /** 152 | * Explode and clean comma separaded setting values 153 | * 154 | * @param string $string 155 | * @param string $delimiter 156 | * @return array 157 | */ 158 | public function explodeAndClean($string, $delimiter = ',') 159 | { 160 | $array = explode($delimiter, $string); // Explode fields to array 161 | $array = array_map('trim', $array); // Trim array's values 162 | $array = array_keys(array_flip($array)); // Remove duplicate fields 163 | return array_filter($array); // Remove empty values from array 164 | } 165 | 166 | private function getVersionData() { 167 | $version = $this->getOption('lit'); 168 | if (empty($version)) { 169 | $version = $this->modx->version['full_version']; 170 | } 171 | return $version; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinymcerte", 3 | "description": "TinyMCE Rich Text Editor", 4 | "main": "./src/js/index.js", 5 | "scripts": { 6 | "build": "webpack --progress --mode production && npm run gettinymce && npm run getlangs", 7 | "dev": "webpack --watch --progress --mode development", 8 | "gettinymce": "cp -r node_modules/tinymce ./assets/components/tinymcerte/mgr/tinymce", 9 | "getlangs": "cp -r node_modules/tinymce-i18n/langs6 ./assets/components/tinymcerte/mgr/tinymce/langs" 10 | }, 11 | "library": { 12 | "name": "TinyMCERTE", 13 | "entry": "index.js", 14 | "dist-web": "tinymcerte.min.js" 15 | }, 16 | "devDependencies": { 17 | "@babel/cli": "^7.23.4", 18 | "@babel/core": "^7.23.7", 19 | "@babel/plugin-transform-modules-commonjs": "^7.23.3", 20 | "@babel/polyfill": "^7.12.1", 21 | "@babel/preset-env": "^7.23.8", 22 | "@babel/register": "^7.23.7", 23 | "babel-loader": "^9.1.3", 24 | "babel-plugin-add-module-exports": "^1.0.4", 25 | "mini-css-extract-plugin": "^2.7.7", 26 | "webpack": "^5.90.0", 27 | "webpack-cli": "^5.1.4", 28 | "webpack-node-externals": "^3.0.0" 29 | }, 30 | "dependencies": { 31 | "autoprefixer": "^10.4.17", 32 | "choices.js": "^11.1.0", 33 | "css-loader": "^6.9.1", 34 | "cssnano": "^6.0.3", 35 | "postcss-loader": "^8.0.0", 36 | "tinymce": "^6.8.6", 37 | "tinymce-i18n": "^25.5.12" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer'), 4 | require('cssnano'), 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/css/tinymcerte.css: -------------------------------------------------------------------------------- 1 | @import "choices.js/public/assets/styles/choices.css"; 2 | 3 | .tox .tox-checkbox__label { 4 | font-size: 14px; 5 | } 6 | .tox .choices { 7 | position: relative; 8 | } 9 | .tox .choices__inner { 10 | appearance: none; 11 | background-color: #fff; 12 | border-color: #eee; 13 | border-radius: 6px; 14 | border-style: solid; 15 | border-width: 1px; 16 | box-shadow: none; 17 | box-sizing: border-box; 18 | color: #222f3e; 19 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif; 20 | font-size: 16px; 21 | line-height: 24px; 22 | margin: 0; 23 | min-height: 34px; 24 | outline: 0; 25 | padding: 5px 5.5px; 26 | resize: none; 27 | width: 100%; 28 | } 29 | 30 | .tox .choices__list small { 31 | font-size: 12px; 32 | opacity: 0.8; 33 | } 34 | 35 | .tox .choices__button { 36 | text-indent: -9999px; 37 | appearance: none; 38 | border: 0; 39 | background-color: transparent; 40 | background-repeat: no-repeat; 41 | background-position: center; 42 | cursor: pointer; 43 | } 44 | 45 | .tox.tox-tinymce-aux { 46 | z-index: 10000; 47 | } -------------------------------------------------------------------------------- /src/js/Plugins/RegisterPlugins.js: -------------------------------------------------------------------------------- 1 | import modxlink from './modxlink/modxlink'; 2 | import modai from './modai/modai'; 3 | 4 | tinymce.PluginManager.add('modxlink', modxlink); 5 | tinymce.PluginManager.add('modai', modai); -------------------------------------------------------------------------------- /src/js/Plugins/modai/modai.js: -------------------------------------------------------------------------------- 1 | export default (editor, url) => { 2 | if (typeof modAI === 'undefined') { 3 | return; 4 | } 5 | 6 | const modAIPromptHandler = () => { 7 | const selectedText = editor.selection.getContent({format: 'text'}); 8 | 9 | return modAI.ui.localChat({ 10 | namespace: 'tinymcerte.modai', 11 | field: editor.id, 12 | key: editor.id, 13 | resource: (MODx.request.a.toLowerCase() === 'resource/update') ? MODx.request.id : undefined, 14 | availableTypes: ['text', 'image'], 15 | context: selectedText, 16 | customCSS: editor.contentCSS, 17 | textActions: { 18 | insert: (msg, modal) => { 19 | editor.insertContent(msg.content); 20 | modal.api.closeModal(); 21 | } 22 | }, 23 | imageActions: { 24 | copy: false, 25 | insert: (msg, modal) => { 26 | editor.insertContent(``); 27 | modal.api.closeModal(); 28 | } 29 | } 30 | }); 31 | } 32 | 33 | const getEnhancePrompts = () => { 34 | try { 35 | return JSON.parse(MODx.config[`tinymcerte.modai.${editor.id}.text.modify_prompts`] || MODx.config[`tinymcerte.modai.global.text.modify_prompts`] || '{}'); 36 | } catch { 37 | return []; 38 | } 39 | } 40 | 41 | const formatEnhancePrompts = (item, disabled) => { 42 | if (item.prompts) { 43 | if (!item.label) return null; 44 | 45 | return { 46 | type: 'nestedmenuitem', 47 | text: item.label, 48 | getSubmenuItems: () => { 49 | return item.prompts.map((n) => { 50 | return formatEnhancePrompts(n, disabled); 51 | }).filter(Boolean); 52 | } 53 | } 54 | } 55 | 56 | if (!item.label || !item.prompt) return null; 57 | 58 | return { 59 | type: 'menuitem', 60 | text: item.label, 61 | disabled: disabled, 62 | onAction: async function () { 63 | await generate(item.prompt); 64 | 65 | } 66 | }; 67 | } 68 | 69 | const generate = async (prompt) => { 70 | const modal = modAIPromptHandler(); 71 | 72 | modal.api.sendMessage(prompt, true); 73 | } 74 | 75 | editor.ui.registry.addButton('modai_generate', { 76 | text: '✦ Prompt', 77 | onAction: () => { 78 | modAIPromptHandler(); 79 | } 80 | }); 81 | 82 | editor.ui.registry.addMenuItem('modai_generate', { 83 | text: '✦ Prompt', 84 | onAction: () => { 85 | modAIPromptHandler(); 86 | } 87 | }); 88 | 89 | editor.ui.registry.addContextMenu('modai_generate', { 90 | update: function (el) { 91 | return 'modai_generate'; 92 | } 93 | }); 94 | 95 | const prompts = getEnhancePrompts(); 96 | if (prompts.length > 0) { 97 | editor.ui.registry.addMenuButton('modai_enhance', { 98 | text: '✦ Modify', 99 | fetch: function (cb) { 100 | const selection = editor.selection.getContent({format: 'text'}); 101 | 102 | cb(prompts.map((prompt) => { 103 | return formatEnhancePrompts(prompt, !selection); 104 | }).filter(Boolean)); 105 | } 106 | }); 107 | 108 | editor.ui.registry.addNestedMenuItem('modai_enhance', { 109 | text: '✦ Modify', 110 | getSubmenuItems: function() { 111 | const selection = editor.selection.getContent({format: 'text'}); 112 | return prompts.map((prompt) => { 113 | return formatEnhancePrompts(prompt, selection, !selection); 114 | }).filter(Boolean); 115 | } 116 | }); 117 | } 118 | return { 119 | getMetadata: function () { 120 | return { 121 | name: "modAI Plugin", 122 | url: url 123 | }; 124 | } 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /src/js/Plugins/modxlink/Data.js: -------------------------------------------------------------------------------- 1 | export default class Data { 2 | constructor(editor) { 3 | this.editor = editor; 4 | window.editor = editor; 5 | this.element = editor.dom.getParent(editor.selection.getStart(), 'a[href]'); 6 | let blocks = this.editor.selection.getSelectedBlocks(); 7 | if (blocks[0].firstElementChild && blocks[0].firstElementChild.nodeName === 'A') { 8 | console.info('Descending into anchor'); 9 | this.element = blocks[0].firstElementChild; 10 | this.editor.selection.select(this.element); 11 | } 12 | 13 | 14 | this.initialData = { 15 | link_text: this.editor.selection.getContent(), 16 | link_title: '', 17 | classes: '', 18 | new_window: false, 19 | 'page_page': '', 20 | 'page_url': '', 21 | 'page_anchor': '', 22 | 'page_parameters': '', 23 | 'url_url': '', 24 | 'email_to': '', 25 | 'email_subject': '', 26 | 'email_body': '', 27 | 'phone_phone': '', 28 | 'file_file': { value: '', metadata: undefined } 29 | }; 30 | 31 | this.activeTab = 'url'; 32 | this.data = this.parseData(); 33 | } 34 | 35 | getData() { 36 | return this.data; 37 | } 38 | 39 | getActiveTab() { 40 | return this.activeTab; 41 | } 42 | 43 | parseData() { 44 | const elementData = this.getElementData(); 45 | this.activeTab = elementData.tab; 46 | 47 | return { 48 | ...(this.initialData), 49 | ...(elementData.data) 50 | }; 51 | } 52 | 53 | getElementData() { 54 | if (!this.element) return {}; 55 | 56 | this.editor.selection.select(this.element); 57 | 58 | const data = { 59 | link_text: this.editor.selection.getContent(), 60 | link_title: '', 61 | classes: '', 62 | new_window: false, 63 | ...(this.initialData || {}) 64 | 65 | }; 66 | 67 | data.link_title = this.element.getAttribute('title') ?? ''; 68 | data.classes = this.element.getAttribute('class') ?? ''; 69 | data.new_window = (this.element.getAttribute('target') === '_blank'); 70 | data.link_text = this.element.innerHTML; 71 | 72 | const linkType = this.element.dataset.linkType; 73 | let url = this.element.getAttribute('href') ?? ''; 74 | 75 | if (linkType === 'page') { 76 | data.page_page = this.element.getAttribute('data-link-page') ?? ''; 77 | data.page_anchor = this.element.getAttribute('data-link-anchor') ?? ''; 78 | data.page_parameters = this.element.getAttribute('data-link-parameters') ?? ''; 79 | 80 | if (data.page_page || data.page_anchor || data.page_parameters) { 81 | data.page_url = url.replace(('#' + data.page_anchor), ''); 82 | data.page_url = data.page_url.replace(('?' + data.page_parameters), ''); 83 | 84 | return { 85 | tab: 'page', 86 | data, 87 | }; 88 | } 89 | } 90 | 91 | if (linkType === 'email') { 92 | if (url.slice(0, 7) === 'mailto:') { 93 | url = url.slice(7); 94 | url = url.split('?'); 95 | 96 | data.email_to = url[0]; 97 | if (url[1]) { 98 | const components = url[1].split('&'); 99 | components.forEach(component => { 100 | component = component.split('='); 101 | if (component[0] === 'subject') { 102 | data.email_subject = decodeURI(component[1]); 103 | } 104 | 105 | if (component[0] === 'body') { 106 | data.email_body = decodeURI(component[1]); 107 | } 108 | }); 109 | } 110 | 111 | return { 112 | tab: 'email', 113 | data, 114 | } 115 | } 116 | } 117 | 118 | if (linkType === 'phone') { 119 | if (url.slice(0, 4) === 'tel:') { 120 | data.phone_phone = url.slice(4); 121 | 122 | return { 123 | tab: 'phone', 124 | data, 125 | } 126 | } 127 | } 128 | 129 | if (linkType === 'file') { 130 | data.file_file = { value: url, metadata: undefined }; 131 | 132 | return { 133 | tab: 'file', 134 | data, 135 | } 136 | } 137 | 138 | data.url_url = url; 139 | 140 | return { 141 | tab: 'url', 142 | data, 143 | }; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/js/Plugins/modxlink/Link.js: -------------------------------------------------------------------------------- 1 | export default class Link { 2 | constructor(editor) { 3 | this.editor = editor; 4 | this.element = editor.dom.getParent(editor.selection.getStart(), 'a[href]') 5 | } 6 | 7 | insertLink(linkText, attributes) { 8 | this.editor.insertContent(this.editor.dom.createHTML('a', attributes, linkText)); 9 | } 10 | 11 | editLink(linkText, attributes) { 12 | if (!this.element) return this.insertLink(linkText, attributes); 13 | 14 | this.editor.focus(); 15 | this.editor.dom.removeAllAttribs(this.element); 16 | 17 | for (let attribute in attributes) { 18 | if (attributes.hasOwnProperty(attribute)) { 19 | this.editor.dom.setAttrib(this.element, attribute, attributes[attribute]); 20 | } 21 | } 22 | 23 | this.element.innerHTML = linkText; 24 | this.editor.selection.select(this.element); 25 | } 26 | 27 | handleLink(linkText, attributes) { 28 | if (!this.element) return this.insertLink(linkText, attributes); 29 | 30 | return this.editLink(linkText, attributes); 31 | } 32 | 33 | static getGeneralAttributes(data, type) { 34 | const attributes = { 35 | 'data-link-type': type 36 | }; 37 | 38 | if (data.link_title) { 39 | attributes.title = data.link_title; 40 | } 41 | 42 | if (data.new_window) { 43 | attributes.target = '_blank'; 44 | } 45 | 46 | if (data.classes) { 47 | attributes.class = data.classes; 48 | } 49 | 50 | return attributes; 51 | } 52 | 53 | savePage(data) { 54 | if (!data.page_page && !data.page_anchor && !data.page_parameters) return; 55 | const attributes = { 56 | ...(Link.getGeneralAttributes(data, 'page')), 57 | 'data-link-page': data.page_page 58 | }; 59 | if (typeof data.page_url === 'undefined') { 60 | data.page_url = `[[~${data.page_page}]]` 61 | } 62 | attributes.href = data.page_url; 63 | 64 | if (data.page_anchor) { 65 | attributes['data-link-anchor'] = data.page_anchor; 66 | attributes.href = `${data.page_url}#${data.page_anchor}`; 67 | } 68 | if (data.page_parameters) { 69 | attributes['data-link-parameters'] = data.page_parameters; 70 | attributes.href = `${attributes.href}?${data.page_parameters}`; 71 | } 72 | 73 | return this.handleLink(data.link_text, attributes); 74 | } 75 | 76 | saveUrl(data) { 77 | if (!data.url_url) return; 78 | 79 | return this.handleLink(data.link_text, { 80 | ...(Link.getGeneralAttributes(data, 'url')), 81 | href: data.url_url 82 | }); 83 | } 84 | 85 | saveEmail(data) { 86 | if (!data.email_to) return; 87 | 88 | let href = `mailto:${data.email_to}`; 89 | const mailAttrs = []; 90 | 91 | if (data.email_subject) { 92 | mailAttrs.push('subject=' + encodeURI(data.email_subject)); 93 | } 94 | 95 | if (data.email_body) { 96 | mailAttrs.push('body=' + encodeURI(data.email_body)); 97 | } 98 | 99 | if (mailAttrs.length > 0) { 100 | href += '?' + mailAttrs.join('&'); 101 | } 102 | 103 | return this.handleLink(data.link_text, { 104 | ...(Link.getGeneralAttributes(data, 'email')), 105 | href 106 | }); 107 | } 108 | 109 | savePhone(data) { 110 | if (!data.phone_phone) return; 111 | 112 | return this.handleLink(data.link_text, { 113 | ...(Link.getGeneralAttributes(data, 'phone')), 114 | href: `tel:${data.phone_phone}` 115 | }); 116 | } 117 | 118 | saveFile(data) { 119 | if (!data.file_file) return; 120 | 121 | return this.handleLink(data.link_text, { 122 | ...(Link.getGeneralAttributes(data, 'file')), 123 | href: data.file_file.value 124 | }); 125 | } 126 | 127 | save(type, data) { 128 | switch (type) { 129 | case 'page': 130 | this.savePage(data); 131 | break; 132 | case 'url': 133 | this.saveUrl(data); 134 | break; 135 | case 'email': 136 | this.saveEmail(data); 137 | break; 138 | case 'phone': 139 | this.savePhone(data); 140 | break; 141 | case 'file': 142 | this.saveFile(data); 143 | break; 144 | } 145 | 146 | this.editor.selection.collapse(false); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/js/Plugins/modxlink/Unlink.js: -------------------------------------------------------------------------------- 1 | const Tools = tinymce.util.Tools; 2 | const TreeWalker = tinymce.dom.TreeWalker; 3 | const RangeUtils = tinymce.dom.RangeUtils; 4 | 5 | const getSelectedElements = function (rootElm, startNode, endNode) { 6 | let walker, node; 7 | const elms = []; 8 | 9 | walker = new TreeWalker(startNode, rootElm); 10 | for (node = startNode; node; node = walker.next()) { 11 | if (node.nodeType === 1) { 12 | elms.push(node); 13 | } 14 | 15 | if (node === endNode) { 16 | break; 17 | } 18 | } 19 | 20 | return elms; 21 | }; 22 | 23 | const unwrapElements = function (editor, elms) { 24 | Tools.each(elms, function (elm) { 25 | editor.dom.remove(elm, true); 26 | }); 27 | 28 | editor.selection.collapse(); 29 | }; 30 | 31 | const isLink = function (elm) { 32 | return elm.nodeName === 'A' && elm.hasAttribute('href'); 33 | }; 34 | 35 | const getParentAnchorOrSelf = function (dom, elm) { 36 | const anchorElm = dom.getParent(elm, isLink); 37 | return anchorElm ? anchorElm : elm; 38 | }; 39 | 40 | const getSelectedAnchors = function (editor) { 41 | let startElm, endElm, rootElm, anchorElms, selection, dom, rng; 42 | 43 | selection = editor.selection; 44 | dom = editor.dom; 45 | rng = selection.getRng(); 46 | startElm = getParentAnchorOrSelf(dom, RangeUtils.getNode(rng.startContainer, rng.startOffset)); 47 | endElm = RangeUtils.getNode(rng.endContainer, rng.endOffset); 48 | rootElm = editor.getBody(); 49 | anchorElms = Tools.grep(getSelectedElements(rootElm, startElm, endElm), isLink); 50 | 51 | return anchorElms; 52 | }; 53 | 54 | export const unlinkSelection = function (editor) { 55 | unwrapElements(editor, getSelectedAnchors(editor)); 56 | }; 57 | 58 | export default unlinkSelection; 59 | -------------------------------------------------------------------------------- /src/js/Plugins/modxlink/modxlink.js: -------------------------------------------------------------------------------- 1 | import Choices from 'choices.js'; 2 | 3 | import Data from './Data'; 4 | import Link from './Link'; 5 | import { unlinkSelection } from './Unlink'; 6 | 7 | export default (editor, url) => { 8 | 9 | editor.options.register('link_class_list', { 10 | processor: 'object[]', 11 | default: [] 12 | }); 13 | 14 | editor.options.register('enable_link_list', { 15 | processor: 'boolean', 16 | default: true 17 | }); 18 | 19 | editor.options.register('link_list', { 20 | processor: 'object[]', 21 | default: [] 22 | }); 23 | const handleClick = () => { 24 | const dataHelper = new Data(editor); 25 | let currentTab = dataHelper.getActiveTab() ?? 'page'; 26 | 27 | const data = dataHelper.getData(); 28 | const lookupCache = {}; 29 | let lookupTimeout = null; 30 | let initData = []; 31 | const formsize = 40; 32 | const choicesData = { 33 | 'page_page': data.page_page, 34 | 'page_url': data.page_url, 35 | }; 36 | 37 | const buildListItems = (inputList, itemCallback, startItems) => { 38 | function appendItems(values, output) 39 | { 40 | output = output || []; 41 | 42 | tinymce.each(values, function (item) { 43 | var menuItem = {text: item.text || item.title}; 44 | 45 | if (item.items) { 46 | menuItem.items = appendItems(item.items); 47 | } else if (item.menu) { 48 | menuItem.items = appendItems(item.menu); 49 | } else { 50 | menuItem.value = item.value.toString(); 51 | 52 | if (itemCallback) { 53 | itemCallback(menuItem); 54 | } 55 | } 56 | 57 | output.push(menuItem); 58 | }); 59 | 60 | return output; 61 | } 62 | 63 | return appendItems(inputList, startItems || []); 64 | }; 65 | 66 | const linkOptions = [{ 67 | type: 'input', 68 | label: 'Link Text', 69 | name: 'link_text', 70 | size: formsize, 71 | }]; 72 | linkOptions.push({ 73 | type: 'input', 74 | name: 'link_title', 75 | label: 'Link Title', 76 | size: formsize, 77 | 78 | }); 79 | if (editor.options.get('link_class_list').length) { 80 | linkOptions.push({ 81 | type: 'listbox', 82 | name: 'classes', 83 | label: 'Classes', 84 | size: formsize, 85 | items: buildListItems( 86 | editor.options.get('link_class_list'), 87 | function (item) { 88 | if (item.value) { 89 | item.textStyle = function () { 90 | return editor.formatter.getCssText({inline: 'a', classes: [item.value]}); 91 | }; 92 | } 93 | } 94 | ), 95 | }); 96 | }else{ 97 | linkOptions.push({ 98 | type: 'input', 99 | name: 'classes', 100 | size: formsize, 101 | label: 'Classes', 102 | }); 103 | } 104 | 105 | linkOptions.push({ 106 | type: 'checkbox', 107 | name: 'new_window', 108 | size: formsize, 109 | label: 'New Window', 110 | }); 111 | 112 | const linkOptionsPanel = { 113 | type: 'panel', 114 | items: linkOptions 115 | }; 116 | let pageSelector = null; 117 | 118 | if (editor.options.get('enable_link_list')) { 119 | pageSelector = { 120 | type: 'listbox', 121 | name: 'page_page', 122 | label: 'Page', 123 | size: formsize, 124 | items: buildListItems( 125 | editor.options.get('link_list') 126 | ) 127 | } 128 | } else { 129 | pageSelector = { 130 | id: 'pagecontainer', 131 | type: 'htmlpanel', 132 | html: '' 133 | }; 134 | } 135 | 136 | const tabPanel = { 137 | type: 'tabpanel', 138 | tabs: [ 139 | { 140 | title: 'Page', 141 | name: 'page', 142 | items: [ 143 | linkOptionsPanel, 144 | pageSelector, 145 | { 146 | type: 'input', 147 | label: 'Anchor Tag', 148 | id: 'page_anchor', 149 | name: 'page_anchor', 150 | size: formsize, 151 | }, 152 | { 153 | type: 'input', 154 | label: 'Extra Params', 155 | id: 'page_parameters', 156 | name: 'page_parameters', 157 | size: formsize, 158 | }, 159 | ] 160 | }, 161 | { 162 | title: 'URL', 163 | name: 'url', 164 | items: [ 165 | linkOptionsPanel, 166 | { 167 | type: 'input', 168 | label: 'URL', 169 | name: 'url_url', 170 | size: formsize, 171 | } 172 | ] 173 | }, 174 | { 175 | title: 'Email', 176 | name: 'email', 177 | items: [ 178 | linkOptionsPanel, 179 | { 180 | type: 'input', 181 | label: 'To', 182 | name: 'email_to', 183 | size: formsize, 184 | }, 185 | { 186 | type: 'input', 187 | label: 'Subject', 188 | size: formsize, 189 | name: 'email_subject', 190 | }, 191 | { 192 | type: 'input', 193 | multiline: true, 194 | label: 'Body', 195 | size: formsize, 196 | name: 'email_body', 197 | } 198 | ] 199 | }, 200 | { 201 | title: 'Phone', 202 | name: 'phone', 203 | items: [ 204 | linkOptionsPanel, 205 | { 206 | type: 'input', 207 | label: 'Phone', 208 | name: 'phone_phone', 209 | size: formsize, 210 | } 211 | ] 212 | }, 213 | { 214 | title: 'File', 215 | name: 'file', 216 | items: [ 217 | linkOptionsPanel, 218 | { 219 | type: 'urlinput', 220 | label: 'File', 221 | name: 'file_file', 222 | size: formsize, 223 | } 224 | ] 225 | } 226 | ] 227 | }; 228 | 229 | let node = editor.selection.getNode() 230 | const linkState = (node.nodeName == "A"); 231 | 232 | let templateInputChoices; 233 | const initChoices = () => { 234 | const input = document.querySelector('#page_url'); 235 | if (input) { 236 | templateInputChoices = new Choices(input, { 237 | removeItemButton: true, 238 | allowHTML: true, 239 | }); 240 | 241 | initLookup(data.page_page); 242 | 243 | input.addEventListener('search', event => { 244 | clearTimeout(lookupTimeout); 245 | lookupTimeout = setTimeout(serverLookup, 200); 246 | }); 247 | 248 | input.addEventListener('choice', event => { 249 | templateInputChoices.setChoices(initData, 'value', 'pagetitle', true); 250 | choicesData.page_page = event.detail.value; 251 | choicesData.page_url = '[[~' + event.detail.value + ']]'; 252 | 253 | if (!data.link_text) { 254 | data.link_text = event.detail.label; 255 | linkText.value(event.detail.label); 256 | } 257 | 258 | const pageAnchorEl = document.getElementById('page_anchor-l'); 259 | if (pageAnchorEl) { 260 | pageAnchorEl.innerText = 'Block on' + event.detail.choice.label; 261 | } 262 | }); 263 | 264 | input.addEventListener('removeItem', event => { 265 | if (templateInputChoices.getValue()) return; 266 | 267 | choicesData.page_page = ''; 268 | choicesData.page_url = ''; 269 | const pageAnchorEl = document.getElementById('page_anchor-l'); 270 | if (pageAnchorEl) { 271 | pageAnchorEl.innerText = 'Block on' + ( MODx?.activePage?.record?.pagetitle || 'Page'); 272 | } 273 | }); 274 | } 275 | } 276 | 277 | // Open window 278 | const win = editor.windowManager.open({ 279 | title: 'Link to', 280 | initialData: data, 281 | buttons: [ 282 | { 283 | text: 'Ok', 284 | type: 'submit', 285 | },{ 286 | text: linkState ? 'Remove Link' : 'Cancel', 287 | name: 'remove', 288 | type: 'custom', 289 | } 290 | ], 291 | onTabChange: (dialogApi, details) => { 292 | currentTab = details.newTabName; 293 | 294 | initChoices(); 295 | }, 296 | onSubmit: (api) => { 297 | const link = new Link(editor); 298 | let data = api.getData(); 299 | if (!editor.options.get('enable_link_list')) { 300 | data = { 301 | ...data, 302 | ...choicesData, 303 | }; 304 | } 305 | link.save(currentTab, data); 306 | api.close(); 307 | }, 308 | onAction: (api, details) => { 309 | if (details.name !== 'remove') return; 310 | 311 | if (linkState) { 312 | const el = editor.dom.getParent(editor.selection.getStart(), 'a[href]'); 313 | editor.selection.select(el); 314 | unlinkSelection(editor); 315 | } 316 | 317 | api.close(); 318 | }, 319 | body: tabPanel, 320 | }); 321 | win.showTab(currentTab); 322 | 323 | document.querySelectorAll('.tox-dialog').forEach((item) => { 324 | item.classList.add('mce--modxlink'); 325 | }); 326 | 327 | 328 | const populateOptions = options => { 329 | const toRemove = templateInputChoices.getValue(); 330 | 331 | const toKeep = []; 332 | options.forEach(option => { 333 | if (typeof toRemove === 'undefined' || toRemove.value !== option.value) { 334 | toKeep.push(option); 335 | } 336 | }); 337 | 338 | templateInputChoices.setChoices(toKeep, 'value', 'pagetitle', true); 339 | }; 340 | const serverLookup = () => { 341 | const query = templateInputChoices.input.value; 342 | if (query in lookupCache) { 343 | populateOptions(lookupCache[query]); 344 | } else { 345 | const resourceSearchUrl = TinyMCERTE.editorConfig.connector_url + '?action=mgr/resource/search' + (MODx.ctx ? ('&wctx=' + MODx.ctx) : '') + '&HTTP_MODAUTH=' + MODx.siteId + '&query=' + query + '&limit=10&sort=pagetitle&dir=ASC'; 346 | fetch(resourceSearchUrl).then(function (response) { 347 | return response.json(); 348 | }).then(function (data) { 349 | populateOptions(data.results); 350 | lookupCache[query] = data.results; 351 | }) 352 | } 353 | }; 354 | 355 | const initLookup = (value) => { 356 | if (value) { 357 | const resourceSearchUrl = TinyMCERTE.editorConfig.connector_url + '?action=mgr/resource/search' + (MODx.ctx ? ('&wctx=' + MODx.ctx) : '') + '&HTTP_MODAUTH=' + MODx.siteId + '&id=' + value + '&limit=1'; 358 | fetch(resourceSearchUrl).then(function (response) { 359 | return response.json(); 360 | }).then(function (data) { 361 | initData = data.results; 362 | templateInputChoices.setValue([{ 363 | id: data.results[0].id, 364 | highlighted: true, 365 | active: true, 366 | selected: true, 367 | label: data.results[0].pagetitle, 368 | value: data.results[0].value, 369 | }]); 370 | }) 371 | } 372 | } 373 | 374 | initChoices(); 375 | } 376 | 377 | editor.ui.registry.addButton('modxlink', { 378 | icon: 'link', 379 | tooltip: 'Insert/edit link', 380 | onAction: handleClick, 381 | stateSelector: 'a[href]' 382 | }); 383 | 384 | editor.ui.registry.addMenuItem('modxlink', { 385 | icon: 'link', 386 | tooltip: 'Insert/edit link', 387 | onAction: handleClick, 388 | stateSelector: 'a[href]' 389 | }); 390 | 391 | const buildLinkList = () => { 392 | const linklistUrl = TinyMCERTE.editorConfig.connector_url + '?action=mgr/resource/gettree' + (MODx.ctx ? ('&wctx=' + MODx.ctx) : '') + '&HTTP_MODAUTH=' + MODx.siteId; 393 | if (editor.options.get('enable_link_list')) { 394 | fetch(linklistUrl).then(function (response) { 395 | return response.json(); 396 | }).then(function (data) { 397 | editor.options.set('link_list', data.results || []); 398 | }); 399 | } 400 | } 401 | buildLinkList(); 402 | 403 | return { 404 | getMetadata: function () { 405 | return { 406 | name: "MODX Link", 407 | url: "https://modx.com" 408 | }; 409 | } 410 | }; 411 | } 412 | -------------------------------------------------------------------------------- /src/js/browser.js: -------------------------------------------------------------------------------- 1 | import './modx/Browser.ext' -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import './modx/TinyMCE.ext' 2 | import './Plugins/RegisterPlugins' -------------------------------------------------------------------------------- /src/js/modx/Browser.ext.js: -------------------------------------------------------------------------------- 1 | Ext.ns('TinyMCERTE'); 2 | TinyMCERTE.browserCallback = function(data) { 3 | if (data) { 4 | window.parent.postMessage({ 5 | mceAction: 'selectFile', 6 | url: data.fullRelativeUrl 7 | }, origin); 8 | } else { 9 | top.tinymce.activeEditor.windowManager.close(); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/js/modx/TinyMCE.ext.js: -------------------------------------------------------------------------------- 1 | Ext.ns('TinyMCERTE'); 2 | TinyMCERTE.Tiny = function (config, editorConfig) { 3 | Ext.apply(this.cfg, editorConfig, {}); 4 | TinyMCERTE.Tiny.superclass.constructor.call(this, config); 5 | }; 6 | Ext.extend(TinyMCERTE.Tiny, Ext.Component, { 7 | cfg: { 8 | selector: '#ta', 9 | document_base_url: MODx.config.base_url, 10 | file_picker_types: 'file image media' 11 | }, 12 | allowDrop: false, 13 | initComponent: function () { 14 | TinyMCERTE.Tiny.superclass.initComponent.call(this); 15 | Ext.onReady(this.render, this); 16 | }, 17 | editor: null, 18 | render: function () { 19 | var that = this; 20 | Ext.apply(this.cfg, TinyMCERTE.editorConfig, {}); 21 | this.cfg.file_picker_callback = this.loadBrowser; 22 | this.cfg.link_list = (this.cfg.enable_link_list) ? this.linkList : null; 23 | this.cfg.init_instance_callback = function (editor) { 24 | that.editor = editor; 25 | editor.on('change', function () { // Keep synced textarea and iframe on each change 26 | tinymce.triggerSave(); 27 | }); 28 | var saveKey = MODx.config.keymap_save || 's'; 29 | editor.addShortcut('ctrl+' + saveKey, '', function () { 30 | var btn = Ext.getCmp('modx-abtn-save'); 31 | if (!btn) return; 32 | if (btn.disabled) return; 33 | if (!btn.keys) return; 34 | var found = false; 35 | Ext.each(btn.keys, function (key) { 36 | if (key.ctrl === true && key.key === saveKey) { 37 | found = true; 38 | return false; 39 | } 40 | }); 41 | if (!found) return; 42 | btn.el.dom.click(); 43 | }); 44 | if (that.allowDrop) { 45 | that.registerDrop(); 46 | } 47 | }; 48 | tinymce.init(this.cfg); 49 | }, 50 | loadBrowser: function (callback, url, meta) { 51 | var browserUrl = MODx.config.manager_url + 'index.php?a=browser&source=' + MODx.config.default_media_source + (MODx.ctx ? ('&ctx=' + MODx.ctx) : '') + (MODx.request.id ? ('&id=' + MODx.request.id) : ''); 52 | tinyMCE.activeEditor.windowManager.openUrl({ 53 | title: _('modx_browser'), 54 | url: browserUrl, 55 | width: 1200, 56 | height: 600, 57 | onMessage: function (api, data) { 58 | if (data.mceAction === 'selectFile') { 59 | callback(data.url, {}); 60 | api.close(); 61 | } 62 | } 63 | }); 64 | }, 65 | registerDrop: function () { 66 | var editor = this.editor; 67 | var fakeDiv = null; 68 | var ddTarget = new Ext.Element(this.editor.getContainer()); 69 | var ddTargetEl = ddTarget.dom; 70 | var insert = { 71 | text: function (text) { 72 | editor.insertContent(text); 73 | editor.focus(); 74 | }, 75 | link: function (id, text) { 76 | editor.insertContent('' + text + ''); 77 | editor.focus(); 78 | }, 79 | image: function (path) { 80 | editor.insertContent(''); 81 | editor.focus(); 82 | } 83 | }; 84 | var dropTarget = new Ext.dd.DropTarget(ddTargetEl, { 85 | ddGroup: 'modx-treedrop-dd', 86 | _notifyEnter: function (ddSource, e, data) { 87 | fakeDiv = Ext.DomHelper.insertAfter(ddTarget, { 88 | tag: 'div', 89 | style: 'position: absolute;top: 0;left: 0;right: 0;bottom: 0;' 90 | }); 91 | ddTarget.frame(); 92 | editor.focus(); 93 | }, 94 | notifyOut: function (ddSource, e, data) { 95 | fakeDiv && fakeDiv.remove(); 96 | ddTarget.on('mouseover', onMouseOver); 97 | }, 98 | notifyDrop: function (ddSource, e, data) { 99 | console.log(data); 100 | fakeDiv && fakeDiv.remove(); 101 | var v = ''; 102 | var win = false; 103 | switch (data.node.attributes.type) { 104 | case 'modResource': 105 | insert.link(data.node.attributes.pk, data.node.text.replace(/\s*<.*>.*<.*>/, '')); 106 | break; 107 | case 'snippet': 108 | win = true; 109 | break; 110 | case 'chunk': 111 | win = true; 112 | break; 113 | case 'tv': 114 | win = true; 115 | break; 116 | case 'file': 117 | var imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'svg']; 118 | var ext = data.node.attributes.text.substring(data.node.attributes.text.lastIndexOf('.') + 1); 119 | if (imageTypes.indexOf(ext) !== -1) { 120 | insert.image(data.node.attributes.url); 121 | } else { 122 | insert.text(data.node.attributes.url); 123 | } 124 | break; 125 | default: 126 | var dh = Ext.getCmp(data.node.attributes.type + '-drop-handler'); 127 | if (dh) { 128 | return dh.handle(data, { 129 | ddTargetEl: ddTargetEl, 130 | cfg: cfg, 131 | iframe: true, 132 | iframeEl: ddTargetEl, 133 | onInsert: insert.text 134 | }); 135 | } 136 | return false; 137 | } 138 | if (win) { 139 | var r = { 140 | pk: data.node.attributes.pk, 141 | classKey: data.node.attributes.classKey, 142 | name: data.node.attributes.name, 143 | output: v, 144 | ddTargetEl: ddTargetEl, 145 | cfg: {onInsert: insert.text}, 146 | iframe: true, 147 | onInsert: insert.text 148 | }; 149 | if (TinyMCERTE.insertWindow) { 150 | TinyMCERTE.insertWindow.destroy(); 151 | TinyMCERTE.insertWindow.close(); 152 | } 153 | TinyMCERTE.insertWindow = MODx.load({ 154 | xtype: 'modx-window-insert-element', 155 | record: r, 156 | closeAction: 'close', 157 | listeners: { 158 | success: { 159 | fn: function () { 160 | }, scope: this 161 | }, 162 | close: { 163 | fn: function () { 164 | this.destroy(); 165 | this.close(); 166 | } 167 | } 168 | } 169 | }); 170 | TinyMCERTE.insertWindow.setValues(r); 171 | TinyMCERTE.insertWindow.show(); 172 | } 173 | return true; 174 | } 175 | }); 176 | dropTarget.addToGroup('modx-treedrop-elements-dd'); 177 | dropTarget.addToGroup('modx-treedrop-sources-dd'); 178 | var onMouseOver = function (e) { 179 | if (Ext.dd.DragDropMgr.dragCurrent) { 180 | dropTarget._notifyEnter(); 181 | ddTarget.un('mouseover', onMouseOver); 182 | } 183 | }; 184 | ddTarget.on('mouseover', onMouseOver); 185 | this.on('destroy', function () { 186 | dropTarget.destroy(); 187 | }); 188 | } 189 | }); 190 | TinyMCERTE.loadForTVs = function () { 191 | new TinyMCERTE.Tiny({ 192 | allowDrop: true 193 | }, { 194 | selector: '.modx-richtext' 195 | }); 196 | }; 197 | MODx.loadRTE = function (id) { 198 | new TinyMCERTE.Tiny({ 199 | allowDrop: true 200 | }, { 201 | selector: '#' + id 202 | }); 203 | }; 204 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 3 | 4 | module.exports = (env, options) => { 5 | const isProd = options.mode === 'production'; 6 | 7 | return { 8 | mode: options.mode, 9 | devtool: isProd ? false : 'source-map', 10 | 11 | entry: { 12 | tinymcerte: [ 13 | '@babel/polyfill', 14 | './src/css/tinymcerte.css', 15 | './src/js/index.js', 16 | ], 17 | browser: './src/js/browser.js', 18 | }, 19 | 20 | output: { 21 | path: path.resolve(__dirname, './assets/components/tinymcerte/mgr'), 22 | filename: '[name].min.js', 23 | clean: {keep: '.gitignore'} 24 | }, 25 | 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.ts$/, 30 | use: 'ts-loader', 31 | exclude: /node_modules/, 32 | }, 33 | { 34 | test: /\.js$/, 35 | exclude: /(node_modules)/, 36 | use: { 37 | loader: 'babel-loader' 38 | } 39 | }, 40 | { 41 | test: /\.(sa|sc|c)ss$/, 42 | use: [ 43 | { 44 | loader: MiniCssExtractPlugin.loader 45 | }, 46 | { 47 | loader: "css-loader", 48 | options: { 49 | url: false, 50 | sourceMap: true 51 | } 52 | }, 53 | { 54 | loader: "postcss-loader" 55 | }, 56 | ] 57 | } 58 | ] 59 | }, 60 | 61 | resolve: { 62 | extensions: [ '.ts', '.js' ], 63 | }, 64 | 65 | plugins: [ 66 | new MiniCssExtractPlugin({ 67 | filename: "tinymcerte.css" 68 | }) 69 | ] 70 | }; 71 | }; 72 | --------------------------------------------------------------------------------