diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 2b79f35b..2072654e 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,25 @@ Wysiwyg 7.x-2.x, xxxx-xx-xx --------------------------- +#1388224 by ksenzee, sun, TwoD: Fixed editors detaching on form submissions. +#682160 by n_vashenko, TwoD: Fixed lists plugin support for TinyMCE. +#1414354 by Merco: Fixed none.js breaks if textarea.js is not loaded. +#1064600 by TwoD: Fixed maximized editors hidden under Drupal's toolbar. +#1405786 by logaritmisk: Fixed CKEditor being wider than parent elements. +#1531896 by Chi: Fixed strict warning for WYMeditor. +#1442226 by robertom: Fixed inverted list button names for WYMeditor. +#1352426 by TwoD, sun: Added install notes (CKEditor edition clarification). +#1112212 by timdiacon, TwoD: Added language direction buttons for CKEditor. +#1398560 by markwittens: Fixed TinyMCE removing the longdesc attribute. +#970452 by smk-ka, sun, TwoD, drzraf: Fixed outdated TinyMCE plugin info. +#1155678 by james.elliott, Jody Lynn, sun: Add Drupal.detachBehaviors support. +#624018 by smk-ka, quartsize, dagmar, nedjo, rickvug, catch, sun: Added Features support. +#1238766 by Dave Reid: Fixed Missing cells in profile plugins table. +#1073106 by scottrouse: Fixed 'Input Format' should be 'Text Format'. +#1153458 by TwoD: Fixed TinyMCE 'Verify HTML' setting ignored. +#1125582 by TwoD: Fixed TinyMCE fullscreen plugin deletes content. +#1078834 by sun: Fixed coding standards errors. +#1173476 by jim0203, sun: Fixed installation instructions in README.txt. Wysiwyg 7.x-2.1, 2011-06-19 diff --git a/LICENSE.txt b/LICENSE.txt index 2c095c8d..d159169d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,274 +1,339 @@ -GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 - Version 2, June 1991 + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, -Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute -verbatim copies of this license document, but changing it is not allowed. + Preamble - Preamble + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. -The licenses for most software are designed to take away your freedom to -share and change it. By contrast, the GNU General Public License is -intended to guarantee your freedom to share and change free software--to -make sure the software is free for all its users. This General Public License -applies to most of the Free Software Foundation's software and to any other -program whose authors commit to using it. (Some other Free Software -Foundation software is covered by the GNU Library General Public License -instead.) You can apply it to your programs, too. + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. -When we speak of free software, we are referring to freedom, not price. Our -General Public Licenses are designed to make sure that you have the -freedom to distribute copies of free software (and charge for this service if -you wish), that you receive source code or can get it if you want it, that you -can change the software or use pieces of it in new free programs; and that -you know you can do these things. + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. -To protect your rights, we need to make restrictions that forbid anyone to -deny you these rights or to ask you to surrender the rights. These restrictions -translate to certain responsibilities for you if you distribute copies of the -software, or if you modify it. + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. -For example, if you distribute copies of such a program, whether gratis or for -a fee, you must give the recipients all the rights that you have. You must make -sure that they, too, receive or can get the source code. And you must show -them these terms so they know their rights. + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. -We protect your rights with two steps: (1) copyright the software, and (2) -offer you this license which gives you legal permission to copy, distribute -and/or modify the software. + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. -Also, for each author's protection and ours, we want to make certain that -everyone understands that there is no warranty for this free software. If the -software is modified by someone else and passed on, we want its recipients -to know that what they have is not the original, so that any problems -introduced by others will not reflect on the original authors' reputations. + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. -Finally, any free program is threatened constantly by software patents. We -wish to avoid the danger that redistributors of a free program will individually -obtain patent licenses, in effect making the program proprietary. To prevent -this, we have made it clear that any patent must be licensed for everyone's -free use or not licensed at all. + The precise terms and conditions for copying, distribution and +modification follow. -The precise terms and conditions for copying, distribution and modification -follow. + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND - MODIFICATION + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". -0. This License applies to any program or other work which contains a notice -placed by the copyright holder saying it may be distributed under the terms -of this General Public License. The "Program", below, refers to any such -program or work, and a "work based on the Program" means either the -Program or any derivative work under copyright law: that is to say, a work -containing the Program or a portion of it, either verbatim or with -modifications and/or translated into another language. (Hereinafter, translation -is included without limitation in the term "modification".) Each licensee is -addressed as "you". +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. -Activities other than copying, distribution and modification are not covered -by this License; they are outside its scope. The act of running the Program is -not restricted, and the output from the Program is covered only if its contents -constitute a work based on the Program (independent of having been made -by running the Program). Whether that is true depends on what the Program -does. + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. -1. You may copy and distribute verbatim copies of the Program's source -code as you receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice and -disclaimer of warranty; keep intact all the notices that refer to this License -and to the absence of any warranty; and give any other recipients of the -Program a copy of this License along with the Program. +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. -You may charge a fee for the physical act of transferring a copy, and you -may at your option offer warranty protection in exchange for a fee. + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: -2. You may modify your copy or copies of the Program or any portion of it, -thus forming a work based on the Program, and copy and distribute such -modifications or work under the terms of Section 1 above, provided that you -also meet all of these conditions: + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. -a) You must cause the modified files to carry prominent notices stating that -you changed the files and the date of any change. + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. -b) You must cause any work that you distribute or publish, that in whole or in -part contains or is derived from the Program or any part thereof, to be -licensed as a whole at no charge to all third parties under the terms of this -License. + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) -c) If the modified program normally reads commands interactively when run, -you must cause it, when started running for such interactive use in the most -ordinary way, to print or display an announcement including an appropriate -copyright notice and a notice that there is no warranty (or else, saying that -you provide a warranty) and that users may redistribute the program under -these conditions, and telling the user how to view a copy of this License. -(Exception: if the Program itself is interactive but does not normally print such -an announcement, your work based on the Program is not required to print -an announcement.) +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. -These requirements apply to the modified work as a whole. If identifiable -sections of that work are not derived from the Program, and can be -reasonably considered independent and separate works in themselves, then -this License, and its terms, do not apply to those sections when you distribute -them as separate works. But when you distribute the same sections as part -of a whole which is a work based on the Program, the distribution of the -whole must be on the terms of this License, whose permissions for other -licensees extend to the entire whole, and thus to each and every part -regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest your rights to -work written entirely by you; rather, the intent is to exercise the right to -control the distribution of derivative or collective works based on the -Program. +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of a -storage or distribution medium does not bring the other work under the scope -of this License. +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. -3. You may copy and distribute the Program (or a work based on it, under -Section 2) in object code or executable form under the terms of Sections 1 -and 2 above provided that you also do one of the following: + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: -a) Accompany it with the complete corresponding machine-readable source -code, which must be distributed under the terms of Sections 1 and 2 above -on a medium customarily used for software interchange; or, + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, -b) Accompany it with a written offer, valid for at least three years, to give -any third party, for a charge no more than your cost of physically performing -source distribution, a complete machine-readable copy of the corresponding -source code, to be distributed under the terms of Sections 1 and 2 above on -a medium customarily used for software interchange; or, + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, -c) Accompany it with the information you received as to the offer to distribute -corresponding source code. (This alternative is allowed only for -noncommercial distribution and only if you received the program in object -code or executable form with such an offer, in accord with Subsection b -above.) + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source code -means all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation and -installation of the executable. However, as a special exception, the source -code distributed need not include anything that is normally distributed (in -either source or binary form) with the major components (compiler, kernel, -and so on) of the operating system on which the executable runs, unless that -component itself accompanies the executable. +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. -If distribution of executable or object code is made by offering access to -copy from a designated place, then offering equivalent access to copy the -source code from the same place counts as distribution of the source code, -even though third parties are not compelled to copy the source along with the -object code. +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. -4. You may not copy, modify, sublicense, or distribute the Program except as -expressly provided under this License. Any attempt otherwise to copy, -modify, sublicense or distribute the Program is void, and will automatically -terminate your rights under this License. However, parties who have received -copies, or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. -5. You are not required to accept this License, since you have not signed it. -However, nothing else grants you permission to modify or distribute the -Program or its derivative works. These actions are prohibited by law if you -do not accept this License. Therefore, by modifying or distributing the -Program (or any work based on the Program), you indicate your acceptance -of this License to do so, and all its terms and conditions for copying, -distributing or modifying the Program or works based on it. + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. -6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the original -licensor to copy, distribute or modify the Program subject to these terms and -conditions. You may not impose any further restrictions on the recipients' -exercise of the rights granted herein. You are not responsible for enforcing -compliance by third parties to this License. + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. -7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), conditions -are imposed on you (whether by court order, agreement or otherwise) that -contradict the conditions of this License, they do not excuse you from the -conditions of this License. If you cannot distribute so as to satisfy -simultaneously your obligations under this License and any other pertinent -obligations, then as a consequence you may not distribute the Program at all. -For example, if a patent license would not permit royalty-free redistribution -of the Program by all those who receive copies directly or indirectly through -you, then the only way you could satisfy both it and this License would be to + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply and -the section as a whole is intended to apply in other circumstances. +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. -It is not the purpose of this section to induce you to infringe any patents or -other property right claims or to contest validity of any such claims; this -section has the sole purpose of protecting the integrity of the free software -distribution system, which is implemented by public license practices. Many -people have made generous contributions to the wide range of software -distributed through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing to -distribute software through any other system and a licensee cannot impose -that choice. +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. -This section is intended to make thoroughly clear what is believed to be a -consequence of the rest of this License. +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. -8. If the distribution and/or use of the Program is restricted in certain -countries either by patents or by copyrighted interfaces, the original copyright -holder who places the Program under this License may add an explicit -geographical distribution limitation excluding those countries, so that -distribution is permitted only in or among countries not thus excluded. In such -case, this License incorporates the limitation as if written in the body of this -License. + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. -9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will be -similar in spirit to the present version, but may differ in detail to address new -problems or concerns. + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. -Each version is given a distinguishing version number. If the Program specifies -a version number of this License which applies to it and "any later version", -you have the option of following the terms and conditions either of that -version or of any later version published by the Free Software Foundation. If -the Program does not specify a version number of this License, you may -choose any version ever published by the Free Software Foundation. +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. -10. If you wish to incorporate parts of the Program into other free programs -whose distribution conditions are different, write to the author to ask for -permission. For software which is copyrighted by the Free Software -Foundation, write to the Free Software Foundation; we sometimes make -exceptions for this. Our decision will be guided by the two goals of -preserving the free status of all derivatives of our free software and of -promoting the sharing and reuse of software generally. + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY -11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT -PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE -STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT -WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND -PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL -NECESSARY SERVICING, REPAIR OR CORRECTION. + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. -12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR -AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR -ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE -LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, -SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OR INABILITY TO USE THE -PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA -OR DATA BEING RENDERED INACCURATE OR LOSSES -SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE -PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN -IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF -THE POSSIBILITY OF SUCH DAMAGES. + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.txt b/README.txt index cc81447a..75d2b14c 100644 --- a/README.txt +++ b/README.txt @@ -18,7 +18,8 @@ To submit bug reports and feature suggestions, or to track changes: -- INSTALLATION -- -* Install as usual, see http://drupal.org/node/70151 for further information. +* Install as usual, see + http://drupal.org/documentation/install/modules-themes/modules-7 * Go to Administration » Configuration » Content authoring » Wysiwyg, and follow the displayed installation instructions to download and install one diff --git a/editors/ckeditor.inc b/editors/ckeditor.inc index 0a56fe08..fcf168ec 100644 --- a/editors/ckeditor.inc +++ b/editors/ckeditor.inc @@ -27,8 +27,11 @@ function wysiwyg_ckeditor_editor() { ), ), ), + 'install note callback' => 'wysiwyg_ckeditor_install_note', 'version callback' => 'wysiwyg_ckeditor_version', 'themes callback' => 'wysiwyg_ckeditor_themes', + 'settings form callback' => 'wysiwyg_ckeditor_settings_form', + 'init callback' => 'wysiwyg_ckeditor_init', 'settings callback' => 'wysiwyg_ckeditor_settings', 'plugin callback' => 'wysiwyg_ckeditor_plugins', 'plugin settings callback' => 'wysiwyg_ckeditor_plugin_settings', @@ -48,6 +51,13 @@ function wysiwyg_ckeditor_editor() { return $editor; } +/** + * Return an install note. + */ +function wysiwyg_ckeditor_install_note() { + return '

' . t('Do NOT download the "CKEditor for Drupal" edition.') . '

'; +} + /** * Detect editor version. * @@ -111,6 +121,69 @@ function wysiwyg_ckeditor_themes($editor, $profile) { } } +/** + * Enhances the editor profile settings form for CKEditor. + * + * Adds support for CKEditor's advanced stylesSets, which are a more advanced + * implementation and combination of block formats and font styles that allow + * to adjust the HTML element, attributes, and CSS styles at once. + * + * @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles + * @see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.config.html#.stylesSet + */ +function wysiwyg_ckeditor_settings_form(&$form, &$form_state) { + if (version_compare($form_state['wysiwyg']['editor']['installed version'], '3.2.1', '>=')) { + // Replace CSS classes element description to explain the advanced syntax. + $form['css']['css_classes']['#description'] = t('Optionally define CSS classes for the "Font style" dropdown list.
Enter one class on each line in the format: !format. Example: !example
If left blank, CSS classes are automatically imported from loaded stylesheet(s).', array( + '!format' => '[label]=[element].[class]', + '!example' => 'Title=h1.title', + )); + $form['css']['css_classes']['#element_validate'][] = 'wysiwyg_ckeditor_settings_form_validate_css_classes'; + } + else { + // Versions below 3.2.1 do not support Font styles at all. + $form['css']['css_classes']['#access'] = FALSE; + } +} + +/** + * #element_validate handler for CSS classes element altered by wysiwyg_ckeditor_settings_form(). + */ +function wysiwyg_ckeditor_settings_form_validate_css_classes($element, &$form_state) { + if (wysiwyg_ckeditor_settings_parse_styles($element['#value']) === FALSE) { + form_error($element, t('The specified CSS classes are syntactically incorrect.')); + } +} + +/** + * Returns an initialization JavaScript for this editor library. + * + * @param array $editor + * The editor library definition. + * @param string $library + * The library variant key from $editor['libraries']. + * @param object $profile + * The (first) wysiwyg editor profile. + * + * @return string + * A string containing inline JavaScript to execute before the editor library + * script is loaded. + */ +function wysiwyg_ckeditor_init($editor) { + // CKEditor unconditionally searches for its library filename in SCRIPT tags + // on the page upon loading the library in order to determine the base path to + // itself. When JavaScript aggregation is enabled, this search fails and all + // relative constructed paths within CKEditor are broken. The library has a + // CKEditor.basePath property, but it is not publicly documented and thus not + // reliable. The official documentation suggests to solve the issue through + // the global window variable. + // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Specifying_the_Editor_Path + $library_path = base_path() . $editor['library path'] . '/'; + return << $GLOBALS['base_url'] . '/', - 'width' => '100%', + 'width' => 'auto', // For better compatibility with smaller textareas. 'resize_minWidth' => 450, 'height' => 420, @@ -167,7 +239,7 @@ function wysiwyg_ckeditor_settings($editor, $config, $theme) { $settings['contentsCss'] = reset(wysiwyg_get_css()); } elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { - $settings['contentsCss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme())); + $settings['contentsCss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); } } else { @@ -175,16 +247,25 @@ function wysiwyg_ckeditor_settings($editor, $config, $theme) { $settings['contentsCss'] = wysiwyg_get_css(); } elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { - $settings['contentsCss'] = explode(',', strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()))); + $settings['contentsCss'] = explode(',', strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL))))); } } } + // Parse and define the styles set for the Styles plugin (3.2.1+). + // @todo This should be a plugin setting, but Wysiwyg does not support + // plugin-specific settings yet. + if (!empty($config['buttons']['default']['Styles']) && version_compare($editor['installed version'], '3.2.1', '>=')) { + if ($styles = wysiwyg_ckeditor_settings_parse_styles($config['css_classes'])) { + $settings['stylesSet'] = $styles; + } + } + if (isset($config['language'])) { $settings['language'] = $config['language']; } if (isset($config['resizing'])) { - // CKEditor tests "!== false", so ensure it is a Boolean. + // CKEditor performs a type-agnostic comparison on this particular setting. $settings['resize_enabled'] = (bool) $config['resizing']; } if (isset($config['toolbar_loc'])) { @@ -240,6 +321,51 @@ function wysiwyg_ckeditor_settings($editor, $config, $theme) { return $settings; } +/** + * Parses CSS classes settings string into a stylesSet JavaScript settings array. + * + * @param string $css_classes + * A string containing CSS class definitions to add to the Style dropdown + * list, separated by newlines. + * + * @return array|false + * An array containing the parsed stylesSet definition, or FALSE on parse + * error. + * + * @see wysiwyg_ckeditor_settings_form() + * @see wysiwyg_ckeditor_settings_form_validate_css_classes() + * + * @todo This should be a plugin setting, but Wysiwyg does not support + * plugin-specific settings yet. + */ +function wysiwyg_ckeditor_settings_parse_styles($css_classes) { + $set = array(); + $input = trim($css_classes); + if (empty($input)) { + return $set; + } + // Handle both Unix and Windows line-endings. + foreach (explode("\n", str_replace("\r", '', $input)) as $line) { + $line = trim($line); + // [label]=[element].[class][.[class]][...] pattern expected. + if (!preg_match('@^.+= *[a-zA-Z0-9]+(\.[a-zA-Z0-9_ -]+)*$@', $line)) { + return FALSE; + } + list($label, $selector) = explode('=', $line, 2); + $classes = explode('.', $selector); + $element = array_shift($classes); + + $style = array(); + $style['name'] = trim($label); + $style['element'] = trim($element); + if (!empty($classes)) { + $style['attributes']['class'] = implode(' ', array_map('trim', $classes)); + } + $set[] = $style; + } + return $set; +} + /** * Build a JS settings array of native external plugins that need to be loaded separately. */ @@ -294,6 +420,7 @@ function wysiwyg_ckeditor_plugins($editor) { 'Bold' => t('Bold'), 'Italic' => t('Italic'), 'Underline' => t('Underline'), 'Strike' => t('Strike-through'), 'JustifyLeft' => t('Align left'), 'JustifyCenter' => t('Align center'), 'JustifyRight' => t('Align right'), 'JustifyBlock' => t('Justify'), + 'BidiLtr' => t('Left-to-right'), 'BidiRtl' => t('Right-to-left'), 'BulletedList' => t('Bullet list'), 'NumberedList' => t('Numbered list'), 'Outdent' => t('Outdent'), 'Indent' => t('Indent'), 'Undo' => t('Undo'), 'Redo' => t('Redo'), @@ -325,6 +452,10 @@ function wysiwyg_ckeditor_plugins($editor) { if (version_compare($editor['installed version'], '3.1.0.4885', '<')) { unset($plugins['default']['buttons']['CreateDiv']); } + if (version_compare($editor['installed version'], '3.4.0.5808', '<')) { + unset($plugins['default']['buttons']['BidiLtr']); + unset($plugins['default']['buttons']['BidiRtl']); + } if (version_compare($editor['installed version'], '3.5.0.6260', '<')) { unset($plugins['default']['buttons']['Iframe']); } diff --git a/editors/epiceditor.inc b/editors/epiceditor.inc new file mode 100644 index 00000000..7c897686 --- /dev/null +++ b/editors/epiceditor.inc @@ -0,0 +1,102 @@ + 'EpicEditor', + 'vendor url' => 'http://oscargodson.github.com/EpicEditor', + 'download url' => 'http://oscargodson.github.com/EpicEditor/docs/downloads/EpicEditor-v0.1.1.zip', + 'libraries' => array( + '' => array( + 'title' => 'Minified', + 'files' => array('js/epiceditor.min.js'), + ), + 'src' => array( + 'title' => 'Source', + 'files' => array('js/epiceditor.js'), + ), + ), + 'version callback' => 'wysiwyg_epiceditor_version', + 'themes callback' => 'wysiwyg_epiceditor_themes', + 'settings callback' => 'wysiwyg_epiceditor_settings', + 'versions' => array( + '0.1.1' => array( + 'js files' => array('epiceditor.js'), + ), + ), + ); + return $editor; +} + +/** + * Detect editor version. + * + * @param $editor + * An array containing editor properties as returned from hook_editor(). + * + * @return + * The installed editor version. + */ +function wysiwyg_epiceditor_version($editor) { + $library = $editor['library path'] . '/js/epiceditor.js'; + if (!file_exists($library)) { + return; + } + // @todo Do not load the entire file; use fgets() instead. + $library = file_get_contents($library, 'r'); + $version = preg_match('%EpicEditor\.version = \'(.*)\'\;%', $library, $matches); + if (!isset($matches[1])) { + return; + } + return $matches[1]; +} + +/** + * Determine available editor themes or check/reset a given one. + * + * @param $editor + * A processed hook_editor() array of editor properties. + * @param $profile + * A wysiwyg editor profile. + * + * @return + * An array of theme names. The first returned name should be the default + * theme name. + */ +function wysiwyg_epiceditor_themes($editor, $profile) { + return array('epic-dark', 'epic-light'); + // @todo Use the preview themes somewhere. + //return array('preview-dark', 'github'); +} + +/** + * Return runtime editor settings for a given wysiwyg profile. + * + * @param $editor + * A processed hook_editor() array of editor properties. + * @param $config + * An array containing wysiwyg editor profile settings. + * @param $theme + * The name of a theme/GUI/skin to use. + * + * @return + * A settings array to be populated in + * Drupal.settings.wysiwyg.configs.{editor} + */ +function wysiwyg_epiceditor_settings($editor, $config, $theme) { + $settings = array( + 'basePath' => base_path() . $editor['library path'], + 'clientSideStorage' => FALSE, + 'theme' => $theme, + //'preview_theme' => '', + ); + return $settings; +} + diff --git a/editors/fckeditor.inc b/editors/fckeditor.inc index 70954da6..27bcb7b3 100644 --- a/editors/fckeditor.inc +++ b/editors/fckeditor.inc @@ -131,8 +131,8 @@ function wysiwyg_fckeditor_settings($editor, $config, $theme) { if ($config['css_setting'] == 'theme') { $settings['EditorAreaCSS'] = implode(',', wysiwyg_get_css()); } - else if ($config['css_setting'] == 'self' && isset($config['css_path'])) { - $settings['EditorAreaCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme())); + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['EditorAreaCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); } } diff --git a/editors/js/ckeditor-3.0.js b/editors/js/ckeditor-3.0.js index d2cf3005..f2889283 100644 --- a/editors/js/ckeditor-3.0.js +++ b/editors/js/ckeditor-3.0.js @@ -23,6 +23,10 @@ Drupal.wysiwyg.editor.init.ckeditor = function(settings) { } } } + // Register Font styles (versions 3.2.1 and above). + if (Drupal.settings.wysiwyg.configs.ckeditor[format].stylesSet) { + CKEDITOR.stylesSet.add(format, Drupal.settings.wysiwyg.configs.ckeditor[format].stylesSet); + } } }; @@ -34,6 +38,8 @@ Drupal.wysiwyg.editor.attach.ckeditor = function(context, params, settings) { // Apply editor instance settings. CKEDITOR.config.customConfig = ''; + var $drupalToolbar = $('#toolbar', Drupal.overlayChild ? window.parent.document : document); + settings.on = { instanceReady: function(ev) { var editor = ev.editor; @@ -125,6 +131,19 @@ Drupal.wysiwyg.editor.attach.ckeditor = function(context, params, settings) { focus: function(ev) { Drupal.wysiwyg.activeId = ev.editor.name; + }, + + afterCommandExec: function(ev) { + // Fix Drupal toolbar obscuring editor toolbar in fullscreen mode. + if (ev.data.name != 'maximize') { + return; + } + if (ev.data.command.state == CKEDITOR.TRISTATE_ON) { + $drupalToolbar.hide(); + } + else { + $drupalToolbar.show(); + } } }; @@ -139,16 +158,19 @@ Drupal.wysiwyg.editor.attach.ckeditor = function(context, params, settings) { * containing all instances or the passed in params.field instance, but * always return an array to simplify all detach functions. */ -Drupal.wysiwyg.editor.detach.ckeditor = function(context, params) { +Drupal.wysiwyg.editor.detach.ckeditor = function (context, params, trigger) { + var method = (trigger == 'serialize') ? 'updateElement' : 'destroy'; if (typeof params != 'undefined') { var instance = CKEDITOR.instances[params.field]; if (instance) { - instance.destroy(); + instance[method](); } } else { for (var instanceName in CKEDITOR.instances) { - CKEDITOR.instances[instanceName].destroy(); + if (CKEDITOR.instances.hasOwnProperty(instanceName)) { + CKEDITOR.instances[instanceName][method](); + } } } }; @@ -208,9 +230,18 @@ Drupal.wysiwyg.editor.instance.ckeditor = { // @todo Don't know if we need this yet. return content; }, + insert: function(content) { content = this.prepareContent(content); CKEDITOR.instances[this.field].insertHtml(content); + }, + + setContent: function (content) { + CKEDITOR.instances[this.field].setData(content); + }, + + getContent: function () { + return CKEDITOR.instances[this.field].getData(); } }; diff --git a/editors/js/epiceditor.js b/editors/js/epiceditor.js new file mode 100644 index 00000000..03c48017 --- /dev/null +++ b/editors/js/epiceditor.js @@ -0,0 +1,38 @@ +(function($) { + +/** + * Attach this editor to a target element. + */ +Drupal.wysiwyg.editor.attach.epiceditor = function (context, params, settings) { + var $target = $('#' + params.field); + var containerId = params.field + '-epiceditor'; + var defaultContent = $target.val(); + $target.hide().after('
'); + + settings.container = containerId; + settings.file = { + defaultContent: defaultContent + }; + settings.theme = { + preview: '/themes/preview/preview-dark.css', + editor: '/themes/editor/' + settings.theme + '.css' + } + var editor = new EpicEditor(settings).load(); + $target.data('epiceditor', editor); +}; + +/** + * Detach a single or all editors. + */ +Drupal.wysiwyg.editor.detach.epiceditor = function (context, params, trigger) { + var $target = $('#' + params.field); + var editor = $target.data('epiceditor'); + + $target.val(editor.exportFile()); + + editor.unload(function () { + $target.show(); + }); +}; + +})(jQuery); diff --git a/editors/js/fckeditor-2.6.js b/editors/js/fckeditor-2.6.js index 4ee2cff7..fd915e3f 100644 --- a/editors/js/fckeditor-2.6.js +++ b/editors/js/fckeditor-2.6.js @@ -21,7 +21,7 @@ Drupal.wysiwyg.editor.attach.fckeditor = function(context, params, settings) { /** * Detach a single or all editors. */ -Drupal.wysiwyg.editor.detach.fckeditor = function(context, params) { +Drupal.wysiwyg.editor.detach.fckeditor = function (context, params, trigger) { var instances = []; if (typeof params != 'undefined' && typeof FCKeditorAPI != 'undefined') { var instance = FCKeditorAPI.GetInstance(params.field); @@ -36,6 +36,11 @@ Drupal.wysiwyg.editor.detach.fckeditor = function(context, params) { for (var instanceName in instances) { var instance = instances[instanceName]; instance.UpdateLinkedField(); + if (trigger == 'serialize') { + // The editor is not being removed from the DOM, so updating the linked + // field is the only action necessary. + continue; + } // Since we already detach the editor and update the textarea, the submit // event handler needs to be removed to prevent data loss (in IE). // FCKeditor uses 2 nested iFrames; instance.EditingArea.Window is the @@ -175,6 +180,16 @@ Drupal.wysiwyg.editor.instance.fckeditor = { var instance = FCKeditorAPI.GetInstance(this.field); // @see FCK.InsertHtml(), FCK.InsertElement() instance.InsertHtml(content); + }, + + getContent: function () { + var instance = FCKeditorAPI.GetInstance(this.field); + return instance.GetData(); + }, + + setContent: function (content) { + var instance = FCKeditorAPI.GetInstance(this.field); + instance.SetHTML(content); } }; diff --git a/editors/js/fckeditor.config.js b/editors/js/fckeditor.config.js index d3fbc2fc..42efc322 100644 --- a/editors/js/fckeditor.config.js +++ b/editors/js/fckeditor.config.js @@ -40,6 +40,19 @@ for (var setting in wysiwygSettings) { } } +// Fix Drupal toolbar obscuring editor toolbar in fullscreen mode. +var oldFitWindowExecute = FCKFitWindow.prototype.Execute; +var $drupalToolbar = window.parent.jQuery('#toolbar', Drupal.overlayChild ? window.parent.window.parent.document : window.parent.document); +FCKFitWindow.prototype.Execute = function() { + oldFitWindowExecute.apply(this, arguments); + if (this.IsMaximized) { + $drupalToolbar.hide(); + } + else { + $drupalToolbar.show(); + } +} + /** * Initialize this editor instance. */ diff --git a/editors/js/jwysiwyg.js b/editors/js/jwysiwyg.js index ae478532..d3e7490f 100644 --- a/editors/js/jwysiwyg.js +++ b/editors/js/jwysiwyg.js @@ -11,15 +11,33 @@ Drupal.wysiwyg.editor.attach.jwysiwyg = function(context, params, settings) { /** * Detach a single or all editors. */ -Drupal.wysiwyg.editor.detach.jwysiwyg = function(context, params) { +Drupal.wysiwyg.editor.detach.jwysiwyg = function (context, params, trigger) { var $field = $('#' + params.field); var editor = $field.data('wysiwyg'); if (typeof editor != 'undefined') { editor.saveContent(); - editor.element.remove(); + if (trigger != 'serialize') { + editor.element.remove(); + } } $field.removeData('wysiwyg'); - $field.show(); + if (trigger != 'serialize') { + $field.show(); + } +}; + +Drupal.wysiwyg.editor.instance.jwysiwyg = { + insert: function (content) { + $('#' + this.field).wysiwyg('insertHtml', content); + }, + + setContent: function (content) { + $('#' + this.field).wysiwyg('setContent', content); + }, + + getContent: function () { + return $('#' + this.field).wysiwyg('getContent'); + } }; })(jQuery); diff --git a/editors/js/markitup.js b/editors/js/markitup.js index 66918114..00e10b97 100644 --- a/editors/js/markitup.js +++ b/editors/js/markitup.js @@ -17,7 +17,10 @@ Drupal.wysiwyg.editor.attach.markitup = function(context, params, settings) { /** * Detach a single or all editors. */ -Drupal.wysiwyg.editor.detach.markitup = function(context, params) { +Drupal.wysiwyg.editor.detach.markitup = function (context, params, trigger) { + if (trigger == 'serialize') { + return; + } if (typeof params != 'undefined') { $('#' + params.field, context).markItUpRemove(); } @@ -26,4 +29,18 @@ Drupal.wysiwyg.editor.detach.markitup = function(context, params) { } }; +Drupal.wysiwyg.editor.instance.markitup = { + insert: function (content) { + $.markItUp({ replaceWith: content }); + }, + + setContent: function (content) { + $('#' + this.field).val(content); + }, + + getContent: function () { + return $('#' + this.field).val(); + } +}; + })(jQuery); diff --git a/editors/js/nicedit.js b/editors/js/nicedit.js index d5d97957..10b7809c 100644 --- a/editors/js/nicedit.js +++ b/editors/js/nicedit.js @@ -30,12 +30,17 @@ Drupal.wysiwyg.editor.attach.nicedit = function(context, params, settings) { * * See Drupal.wysiwyg.editor.detach.none() for a full description of this hook. */ -Drupal.wysiwyg.editor.detach.nicedit = function(context, params) { +Drupal.wysiwyg.editor.detach.nicedit = function (context, params, trigger) { if (typeof params != 'undefined') { var instance = nicEditors.findEditor(params.field); if (instance) { - instance.ne.removeInstance(params.field); - instance.ne.removePanel(); + if (trigger == 'serialize') { + instance.saveContent(); + } + else { + instance.ne.removeInstance(params.field); + instance.ne.removePanel(); + } } } else { @@ -43,10 +48,17 @@ Drupal.wysiwyg.editor.detach.nicedit = function(context, params) { // Save contents of all editors back into textareas. var instances = nicEditors.editors[e].nicInstances; for (var i = 0; i < instances.length; i++) { - instances[i].remove(); + if (trigger == 'serialize') { + instances[i].saveContent(); + } + else { + instances[i].remove(); + } } // Remove all editor instances. - nicEditors.editors[e].nicInstances = []; + if (trigger != 'serialize') { + nicEditors.editors[e].nicInstances = []; + } } } }; @@ -62,7 +74,7 @@ Drupal.wysiwyg.editor.instance.nicedit = { // IE. if (document.selection) { editingArea.focus(); - sel.createRange().text = content; + sel.createRange().pasteHTML(content); } else { // Convert selection to a range. @@ -89,6 +101,14 @@ Drupal.wysiwyg.editor.instance.nicedit = { // Only fragment children are inserted. range.insertNode(fragment); } + }, + + setContent: function (content) { + nicEditors.findEditor(this.field).setContent(content); + }, + + getContent: function () { + return nicEditors.findEditor(this.field).getContent(); } }; diff --git a/editors/js/none.js b/editors/js/none.js index 34020240..762f7fb2 100644 --- a/editors/js/none.js +++ b/editors/js/none.js @@ -17,7 +17,7 @@ Drupal.wysiwyg.editor.attach.none = function(context, params, settings) { if (params.resizable) { var $wrapper = $('#' + params.field).parents('.form-textarea-wrapper:first'); $wrapper.addClass('resizable'); - if (Drupal.behaviors.textarea.attach) { + if (Drupal.behaviors.textarea) { Drupal.behaviors.textarea.attach(); } } @@ -26,6 +26,9 @@ Drupal.wysiwyg.editor.attach.none = function(context, params, settings) { /** * Detach a single or all editors. * + * The editor syncs its contents back to the original field before its instance + * is removed. + * * @param context * A DOM element, supplied by Drupal.attachBehaviors(). * @param params @@ -33,9 +36,18 @@ Drupal.wysiwyg.editor.attach.none = function(context, params, settings) { * only the editor instance in params.field should be detached. Otherwise, * all editors should be detached and saved, so they can be submitted in * AJAX/AHAH applications. + * @param trigger + * A string describing why the editor is being detached. + * Possible triggers are: + * - unload: (default) Another or no editor is about to take its place. + * - move: Currently expected to produce the same result as unload. + * - serialize: The form is about to be serialized before an AJAX request or + * a normal form submission. If possible, perform a quick detach and leave + * the editor's GUI elements in place to avoid flashes or scrolling issues. + * @see Drupal.detachBehaviors */ -Drupal.wysiwyg.editor.detach.none = function(context, params) { - if (typeof params != 'undefined') { +Drupal.wysiwyg.editor.detach.none = function (context, params, trigger) { + if (typeof params != 'undefined' && (trigger != 'serialize')) { var $wrapper = $('#' + params.field).parents('.form-textarea-wrapper:first'); $wrapper.removeOnce('textarea').removeClass('.resizable-textarea') .find('.grippie').remove(); @@ -65,6 +77,14 @@ Drupal.wysiwyg.editor.instance.none = { else { editor.value += content; } + }, + + setContent: function (content) { + $('#' + this.field).val(content); + }, + + getContent: function () { + return $('#' + this.field).val(); } }; diff --git a/editors/js/openwysiwyg.js b/editors/js/openwysiwyg.js index 89a5337e..a01e8a0b 100644 --- a/editors/js/openwysiwyg.js +++ b/editors/js/openwysiwyg.js @@ -23,6 +23,19 @@ WYSIWYG.getEditor = function (n) { (function($) { +// Fix Drupal toolbar obscuring editor toolbar in fullscreen mode. +var oldMaximize = WYSIWYG.maximize; +WYSIWYG.maximize = function (n) { +var $drupalToolbar = $('#toolbar', Drupal.overlayChild ? window.parent.document : document); + oldMaximize.apply(this, arguments); + if (this.maximized[n]) { + $drupalToolbar.hide(); + } + else { + $drupalToolbar.show(); + } +} + /** * Attach this editor to a target element. */ @@ -45,24 +58,84 @@ Drupal.wysiwyg.editor.attach.openwysiwyg = function(context, params, settings) { /** * Detach a single or all editors. */ -Drupal.wysiwyg.editor.detach.openwysiwyg = function(context, params) { +Drupal.wysiwyg.editor.detach.openwysiwyg = function (context, params, trigger) { if (typeof params != 'undefined') { var instance = WYSIWYG.config[params.field]; if (typeof instance != 'undefined') { WYSIWYG.updateTextArea(params.field); - jQuery('#wysiwyg_div_' + params.field).remove(); - delete instance; + if (trigger != 'serialize') { + jQuery('#wysiwyg_div_' + params.field).remove(); + delete instance; + } + } + if (trigger != 'serialize') { + jQuery('#' + params.field).show(); } - jQuery('#' + params.field).show(); } else { jQuery.each(WYSIWYG.config, function(field) { WYSIWYG.updateTextArea(field); - jQuery('#wysiwyg_div_' + field).remove(); - delete this; - jQuery('#' + field).show(); + if (trigger != 'serialize') { + jQuery('#wysiwyg_div_' + field).remove(); + delete this; + jQuery('#' + field).show(); + } }); } }; +/** + * Instance methods for openWYSIWYG. + */ +Drupal.wysiwyg.editor.instance.openwysiwyg = { + insert: function (content) { + // If IE has dropped focus content will be inserted at the top of the page. + $('#wysiwyg' + this.field).contents().find('body').focus(); + WYSIWYG.insertHTML(content, this.field); + }, + + setContent: function (content) { + // Based on openWYSIWYG's _generate() method. + var doc = WYSIWYG.getEditorWindow(this.field).document; + if (WYSIWYG.config[this.field].ReplaceLineBreaks) { + content = content.replace(/\n\r|\n/ig, '
'); + } + if (WYSIWYG.viewTextMode[this.field]) { + var html = document.createTextNode(content); + doc.body.innerHTML = ''; + doc.body.appendChild(html); + } + else { + doc.open(); + doc.write(content); + doc.close(); + } + }, + + getContent: function () { + // Based on openWYSIWYG's updateTextarea() method. + var content = ''; + var doc = WYSIWYG.getEditorWindow(this.field).document; + if (WYSIWYG.viewTextMode[this.field]) { + if (WYSIWYG_Core.isMSIE) { + content = doc.body.innerText; + } + else { + var range = doc.body.ownerDocument.createRange(); + range.selectNodeContents(doc.body); + content = range.toString(); + } + } + else { + content = doc.body.innerHTML; + } + content = WYSIWYG.stripURLPath(this.field, content); + content = WYSIWYG_Core.replaceRGBWithHexColor(content); + if (WYSIWYG.config[this.field].ReplaceLineBreaks) { + content = content.replace(/(\r\n)|(\n)/ig, ''); + } + return content; + } +}; + })(jQuery); diff --git a/editors/js/tinymce-2.js b/editors/js/tinymce-2.js index 088021f7..61a60ade 100644 --- a/editors/js/tinymce-2.js +++ b/editors/js/tinymce-2.js @@ -10,18 +10,8 @@ * An object containing editor settings for each input format. */ Drupal.wysiwyg.editor.init.tinymce = function(settings) { - // If JS compression is enabled, TinyMCE is unable to autodetect its global - // settinge, hence we need to define them manually. - // @todo Move global library settings somewhere else. - tinyMCE.baseURL = settings.global.editorBasePath; - tinyMCE.srcMode = (settings.global.execMode == 'src' ? '_src' : ''); - tinyMCE.gzipMode = (settings.global.execMode == 'gzip'); - // Initialize editor configurations. for (var format in settings) { - if (format == 'global') { - continue; - } tinyMCE.init(settings[format]); if (Drupal.settings.wysiwyg.plugins[format]) { // Load native external plugins. @@ -67,7 +57,7 @@ Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) { * * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook. */ -Drupal.wysiwyg.editor.detach.tinymce = function(context, params) { +Drupal.wysiwyg.editor.detach.tinymce = function (context, params, trigger) { if (typeof params != 'undefined') { tinyMCE.removeMCEControl(tinyMCE.getEditorId(params.field)); $('#' + params.field).removeAttr('style'); diff --git a/editors/js/tinymce-3.js b/editors/js/tinymce-3.js index b38f5238..83bae13a 100644 --- a/editors/js/tinymce-3.js +++ b/editors/js/tinymce-3.js @@ -11,19 +11,21 @@ * An object containing editor settings for each input format. */ Drupal.wysiwyg.editor.init.tinymce = function(settings) { - // If JS compression is enabled, TinyMCE is unable to autodetect its global - // settinge, hence we need to define them manually. - // @todo Move global library settings somewhere else. - tinyMCE.baseURL = settings.global.editorBasePath; - tinyMCE.srcMode = (settings.global.execMode == 'src' ? '_src' : ''); - tinyMCE.gzipMode = (settings.global.execMode == 'gzip'); + // Fix Drupal toolbar obscuring editor toolbar in fullscreen mode. + var $drupalToolbar = $('#toolbar', Drupal.overlayChild ? window.parent.document : document); + tinyMCE.onAddEditor.add(function (mgr, ed) { + if (ed.id == 'mce_fullscreen') { + $drupalToolbar.hide(); + } + }); + tinyMCE.onRemoveEditor.add(function (mgr, ed) { + if (ed.id == 'mce_fullscreen') { + $drupalToolbar.show(); + } + }); // Initialize editor configurations. for (var format in settings) { - if (format == 'global') { - continue; - }; - tinyMCE.init(settings[format]); if (Drupal.settings.wysiwyg.plugins[format]) { // Load native external plugins. // Array syntax required; 'native' is a predefined token in JavaScript. @@ -50,6 +52,8 @@ Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) { ed.onEvent.add(function(ed, e) { Drupal.wysiwyg.activeId = ed.id; }); + // Indicate that the DOM has been loaded (in case of Ajax). + tinymce.dom.Event.domLoaded = true; // Make toolbar buttons wrappable (required for IE). ed.onPostRender.add(function (ed) { var $toolbar = $('
'); @@ -79,20 +83,24 @@ Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) { * * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook. */ -Drupal.wysiwyg.editor.detach.tinymce = function(context, params) { +Drupal.wysiwyg.editor.detach.tinymce = function (context, params, trigger) { if (typeof params != 'undefined') { var instance = tinyMCE.get(params.field); if (instance) { instance.save(); - instance.remove(); + if (trigger != 'serialize') { + instance.remove(); + } } } else { // Save contents of all editors back into textareas. tinyMCE.triggerSave(); - // Remove all editor instances. - for (var instance in tinyMCE.editors) { - tinyMCE.editors[instance].remove(); + if (trigger != 'serialize') { + // Remove all editor instances. + for (var instance in tinyMCE.editors) { + tinyMCE.editors[instance].remove(); + } } } }; @@ -138,16 +146,18 @@ Drupal.wysiwyg.editor.instance.tinymce = { // Attach: Replace plain text with HTML representations. ed.onBeforeSetContent.add(function(ed, data) { + var editorId = (ed.id == 'mce_fullscreen' ? ed.getParam('fullscreen_editor_id') : ed.id); if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') { - data.content = Drupal.wysiwyg.plugins[plugin].attach(data.content, pluginSettings, ed.id); + data.content = Drupal.wysiwyg.plugins[plugin].attach(data.content, pluginSettings, editorId); data.content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(data.content); } }); // Detach: Replace HTML representations with plain text. ed.onGetContent.add(function(ed, data) { + var editorId = (ed.id == 'mce_fullscreen' ? ed.getParam('fullscreen_editor_id') : ed.id); if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') { - data.content = Drupal.wysiwyg.plugins[plugin].detach(data.content, pluginSettings, ed.id); + data.content = Drupal.wysiwyg.plugins[plugin].detach(data.content, pluginSettings, editorId); } }); @@ -175,7 +185,7 @@ Drupal.wysiwyg.editor.instance.tinymce = { }, openDialog: function(dialog, params) { - var instanceId = this.isFullscreen() ? 'mce_fullscreen' : this.field; + var instanceId = this.getInstanceId(); var editor = tinyMCE.get(instanceId); editor.windowManager.open({ file: dialog.url + '/' + instanceId, @@ -186,8 +196,7 @@ Drupal.wysiwyg.editor.instance.tinymce = { }, closeDialog: function(dialog) { - var instanceId = this.isFullscreen() ? 'mce_fullscreen' : this.field; - var editor = tinyMCE.get(instanceId); + var editor = tinyMCE.get(this.getInstanceId()); editor.windowManager.close(dialog); }, @@ -222,13 +231,25 @@ Drupal.wysiwyg.editor.instance.tinymce = { insert: function(content) { content = this.prepareContent(content); - var instanceId = this.isFullscreen() ? 'mce_fullscreen' : this.field; - tinyMCE.execInstanceCommand(instanceId, 'mceInsertContent', false, content); + tinyMCE.execInstanceCommand(this.getInstanceId(), 'mceInsertContent', false, content); + }, + + setContent: function (content) { + content = this.prepareContent(content); + tinyMCE.execInstanceCommand(this.getInstanceId(), 'mceSetContent', false, content); + }, + + getContent: function () { + return tinyMCE.get(this.getInstanceId()).getContent(); }, isFullscreen: function() { // TinyMCE creates a completely new instance for fullscreen mode. return tinyMCE.activeEditor.id == 'mce_fullscreen' && tinyMCE.activeEditor.getParam('fullscreen_editor_id') == this.field; + }, + + getInstanceId: function () { + return this.isFullscreen() ? 'mce_fullscreen' : this.field; } }; diff --git a/editors/js/whizzywig-56.js b/editors/js/whizzywig-56.js index 229a70b2..3fc2fe57 100644 --- a/editors/js/whizzywig-56.js +++ b/editors/js/whizzywig-56.js @@ -80,39 +80,28 @@ Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) { // Attach editor. makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all')); // Whizzywig fails to detect and set initial textarea contents. - var instance = $('#whizzy' + params.field).get(0); - if (instance) { - instance.contentWindow.document.body.innerHTML = tidyD($field.val()); - } + $('#whizzy' + params.field).contents().find('body').html(tidyD($field.val())); }; /** * Detach a single or all editors. */ -Drupal.wysiwyg.editor.detach.whizzywig = function(context, params) { +Drupal.wysiwyg.editor.detach.whizzywig = function (context, params, trigger) { var detach = function (index) { - var id = whizzies[index]; - var instance = $('#whizzy' + id).get(0); - if (!instance) { - return; - } - var editingArea = instance.contentWindow.document; - var $field = $('#' + id); - // Whizzywig shows the original textarea in source mode. - if ($field.css('display') == 'block') { - editingArea.body.innerHTML = $field.val(); - } + var id = whizzies[index], $field = $('#' + id), instance = Drupal.wysiwyg.instances[id]; // Save contents of editor back into textarea. - $field.val(tidyH(editingArea)); + $field.val(instance.getContent()); + // If the editor is just being serialized (not detached), our work is done. + if (trigger == 'serialize') { + return; + } // Remove editor instance. $('#' + id + '-whizzywig').remove(); whizzies.splice(index, 1); // Restore original textarea styling. - var originalValues = Drupal.wysiwyg.instances[id]; - $field.removeAttr('style'); - $field.attr('style', originalValues.originalStyle); + $field.removeAttr('style').attr('style', instance.originalStyle); }; if (typeof params != 'undefined') { @@ -130,4 +119,37 @@ Drupal.wysiwyg.editor.detach.whizzywig = function(context, params) { } }; +/** + * Instance methods for Whizzywig. + */ +Drupal.wysiwyg.editor.instance.whizzywig = { + insert: function (content) { + // Whizzywig executes any string beginning with 'js:'. + insHTML(content.replace(/^js:/, 'js:')); + }, + + setContent: function (content) { + // Whizzywig shows the original textarea in source mode. + if ($field.css('display') == 'block') { + $('#' + this.field).val(content); + } + else { + var doc = $('#whizzy' + this.field).contents()[0]; + doc.open(); + doc.write(content); + doc.close(); + } + }, + + getContent: function () { + // Whizzywig's tidyH() expects a document node. Clone the editing iframe's + // document so tidyH() won't mess with it if this gets called while editing. + var clone = $($('#whizzy' + this.field).contents()[0].documentElement).clone()[0].ownerDocument; + // Whizzywig shows the original textarea in source mode so update the body. + if ($field.css('display') == 'block') { + clone.body.innerHTML = $('#' + this.field).val(); + } + return tidyH(clone); + } +}; })(jQuery); diff --git a/editors/js/whizzywig-60.js b/editors/js/whizzywig-60.js index dc995f6c..bbc6e649 100644 --- a/editors/js/whizzywig-60.js +++ b/editors/js/whizzywig-60.js @@ -29,42 +29,31 @@ Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) { // Attach editor. makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all')); // Whizzywig fails to detect and set initial textarea contents. - var instance = $('#whizzy' + params.field).get(0); - if (instance) { - instance.contentWindow.document.body.innerHTML = tidyD($field.val()); - } + $('#whizzy' + params.field).contents().find('body').html(tidyD($field.val())); }; /** * Detach a single or all editors. */ -Drupal.wysiwyg.editor.detach.whizzywig = function(context, params) { +Drupal.wysiwyg.editor.detach.whizzywig = function (context, params, trigger) { var detach = function (index) { - var id = whizzies[index]; - var instance = $('#whizzy' + id).get(0); - if (!instance) { - return; - } - var editingArea = instance.contentWindow.document; - var $field = $('#' + id); - // Whizzywig shows the original textarea in source mode. - if ($field.css('display') == 'block') { - editingArea.body.innerHTML = $field.val(); - } + var id = whizzies[index], $field = $('#' + id), instance = Drupal.wysiwyg.instances[id]; // Save contents of editor back into textarea. - $field.val(tidyH(editingArea)); + $field.val(instance.getContent()); + // If the editor is just being serialized (not detached), our work is done. + if (trigger == 'serialize') { + return; + } // Move original textarea back to its previous location. - $container = $('#CONTAINER' + id); + var $container = $('#CONTAINER' + id); $field.insertBefore($container); // Remove editor instance. $container.remove(); whizzies.splice(index, 1); // Restore original textarea styling. - var originalValues = Drupal.wysiwyg.instances[id]; - $field.removeAttr('style'); - $field.attr('style', originalValues.originalStyle); + $field.removeAttr('style').attr('style', instance.originalStyle); } if (typeof params != 'undefined') { @@ -82,4 +71,37 @@ Drupal.wysiwyg.editor.detach.whizzywig = function(context, params) { } }; +/** + * Instance methods for Whizzywig. + */ +Drupal.wysiwyg.editor.instance.whizzywig = { + insert: function (content) { + // Whizzywig executes any string beginning with 'js:'. + insHTML(content.replace(/^js:/, 'js:')); + }, + + setContent: function (content) { + // Whizzywig shows the original textarea in source mode. + if ($field.css('display') == 'block') { + $('#' + this.field).val(content); + } + else { + var doc = $('#whizzy' + this.field).contents()[0]; + doc.open(); + doc.write(content); + doc.close(); + } + }, + + getContent: function () { + // Whizzywig's tidyH() expects a document node. Clone the editing iframe's + // document so tidyH() won't mess with it if this gets called while editing. + var clone = $($('#whizzy' + this.field).contents()[0].documentElement).clone()[0].ownerDocument; + // Whizzywig shows the original textarea in source mode so update the body. + if ($field.css('display') == 'block') { + clone.body.innerHTML = $('#' + this.field).val(); + } + return tidyH(clone); + } +}; })(jQuery); diff --git a/editors/js/whizzywig.js b/editors/js/whizzywig.js index e98bc4da..e89ac5f0 100644 --- a/editors/js/whizzywig.js +++ b/editors/js/whizzywig.js @@ -71,41 +71,28 @@ Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) { // Attach editor. makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all')); // Whizzywig fails to detect and set initial textarea contents. - var instance = $('#whizzy' + params.field).get(0); - if (instance) { - instance.contentWindow.document.body.innerHTML = tidyD($field.val()); - } + $('#whizzy' + params.field).contents().find('body').html(tidyD($field.val())); }; /** * Detach a single or all editors. */ -Drupal.wysiwyg.editor.detach.whizzywig = function(context, params) { +Drupal.wysiwyg.editor.detach.whizzywig = function (context, params, trigger) { var detach = function (index) { - var id = whizzies[index]; - var instance = $('#whizzy' + id).get(0); - if (!instance) { - return; - } - var body = instance.contentWindow.document.body; - var $field = $('#' + id); - // Whizzywig shows the original textarea in source mode. - if ($field.css('display') == 'block') { - body.innerHTML = $field.val(); - } - body.innerHTML = tidyH(body.innerHTML); + var id = whizzies[index], $field = $('#' + id), instance = Drupal.wysiwyg.instances[id]; // Save contents of editor back into textarea. - $field.val(window.get_xhtml ? get_xhtml(body) : body.innerHTML); - $field.val($field.val().replace(location.href + '#', '#')); + $field.val(instance.getContent()); + // If the editor is just being serialized (not detached), our work is done. + if (trigger == 'serialize') { + return; + } // Remove editor instance. $('#' + id + '-whizzywig').remove(); whizzies.splice(index, 1); // Restore original textarea styling. - var originalValues = Drupal.wysiwyg.instances[id]; - $field.removeAttr('style'); - $field.attr('style', originalValues.originalStyle); + $field.removeAttr('style').attr('style', instance.originalStyle); }; if (typeof params != 'undefined') { @@ -123,4 +110,45 @@ Drupal.wysiwyg.editor.detach.whizzywig = function(context, params) { } }; +/** + * Instance methods for Whizzywig. + */ +Drupal.wysiwyg.editor.instance.whizzywig = { + insert: function (content) { + // Whizzywig executes any string beginning with 'js:'. + insHTML(content.replace(/^js:/, 'js:')); + }, + + setContent: function (content) { + var $field = $('#' + this.field); + // Whizzywig shows the original textarea in source mode. + if ($field.css('display') == 'block') { + $field.val(content); + } + else { + var doc = $('#whizzy' + this.field).contents()[0]; + doc.open(); + doc.write(content); + doc.close(); + } + }, + + getContent: function () { + var $field = $('#' + this.field), + // Whizzywig shows the original textarea in source mode. + content = ($field.css('display') == 'block' ? + $field.val() : $('#whizzy' + this.field).contents().find('body').html() + ); + + content = tidyH(content); + // Whizzywig's get_xhtml() addon, if defined, expects a DOM node. + if ($.isFunction(window.get_xhtml)) { + var pre = document.createElement('pre'); + pre.innerHTML = content; + content = get_xhtml(pre); + } + return content.replace(location.href + '#', '#'); + } +}; + })(jQuery); diff --git a/editors/js/wymeditor.js b/editors/js/wymeditor.js index ed667848..4989dc60 100644 --- a/editors/js/wymeditor.js +++ b/editors/js/wymeditor.js @@ -19,37 +19,55 @@ Drupal.wysiwyg.editor.attach.wymeditor = function (context, params, settings) { /** * Detach a single or all editors. */ -Drupal.wysiwyg.editor.detach.wymeditor = function (context, params) { +Drupal.wysiwyg.editor.detach.wymeditor = function (context, params, trigger) { if (typeof params != 'undefined') { var $field = $('#' + params.field); var index = $field.data(WYMeditor.WYM_INDEX); if (typeof index != 'undefined') { var instance = WYMeditor.INSTANCES[index]; instance.update(); - $(instance._box).remove(); - $(instance._element).show(); - delete instance; + if (trigger != 'serialize') { + $(instance._box).remove(); + $(instance._element).show(); + delete instance; + } + } + if (trigger != 'serialize') { + $field.show(); } - $field.show(); } else { jQuery.each(WYMeditor.INSTANCES, function () { this.update(); - $(this._box).remove(); - $(this._element).show(); - delete this; + if (trigger != 'serialize') { + $(this._box).remove(); + $(this._element).show(); + delete this; + } }); } }; Drupal.wysiwyg.editor.instance.wymeditor = { insert: function (content) { + this.getInstance().insert(content); + }, + + setContent: function (content) { + this.getInstance().html(content); + }, + + getContent: function () { + return this.getInstance().xhtml(); + }, + + getInstance: function () { var $field = $('#' + this.field); var index = $field.data(WYMeditor.WYM_INDEX); if (typeof index != 'undefined') { - var instance = WYMeditor.INSTANCES[index]; - instance.insert(content); + return WYMeditor.INSTANCES[index]; } + return null; } }; diff --git a/editors/js/yui.js b/editors/js/yui.js index ad8be368..3f4e7c63 100644 --- a/editors/js/yui.js +++ b/editors/js/yui.js @@ -2,12 +2,70 @@ /** * Attach this editor to a target element. + * + * Since buttons must be added before the editor is rendered, we add plugins + * buttons on attach event rather than in init. */ Drupal.wysiwyg.editor.attach.yui = function(context, params, settings) { // Apply theme. $('#' + params.field).parent().addClass('yui-skin-' + settings.theme); + + // Load plugins stylesheet. + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + settings.extracss += settings.extracss+' @import "'+Drupal.settings.wysiwyg.plugins[params.format].drupal[plugin].css+'"; '; + } + // Attach editor. var editor = new YAHOO.widget.Editor(params.field, settings); + + editor.on('toolbarLoaded', function() { + // Load Drupal plugins. + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + Drupal.wysiwyg.instances[params.field].addPlugin(plugin, Drupal.settings.wysiwyg.plugins[params.format].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]); + } + }); + + // Allow plugins to act on setEditorHTML. + var oldSetEditorHTML = editor.setEditorHTML; + editor.setEditorHTML = function (content) { + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + var pluginSettings = Drupal.settings.wysiwyg.plugins.drupal[plugin]; + if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') { + content = Drupal.wysiwyg.plugins[plugin].attach(content, pluginSettings, params.field); + content = Drupal.wysiwyg.instances[params.field].prepareContent(content); + } + } + oldSetEditorHTML.call(this, content); + }; + + // Allow plugins to act on getEditorHTML. + var oldGetEditorHTML = editor.getEditorHTML; + editor.getEditorHTML = function () { + var content = oldGetEditorHTML.call(this); + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + var pluginSettings = Drupal.settings.wysiwyg.plugins.drupal[plugin]; + if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') { + content = Drupal.wysiwyg.plugins[plugin].detach(content, pluginSettings, params.field); + } + } + return content; + } + + // Reload the editor contents to give Drupal plugins a chance to act. + editor.on('editorContentLoaded', function (e) { + e.target.setEditorHTML(oldGetEditorHTML.call(e.target)); + }); + + editor.on('afterNodeChange', function (e) { + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) { + if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') { + if (Drupal.wysiwyg.plugins[plugin].isNode(e.target._getSelectedElement())) { + this.toolbar.selectButton(plugin); + } + } + } + }); + editor.render(); }; @@ -16,20 +74,72 @@ Drupal.wysiwyg.editor.attach.yui = function(context, params, settings) { * * See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook. */ -Drupal.wysiwyg.editor.detach.yui = function(context, params) { +Drupal.wysiwyg.editor.detach.yui = function (context, params, trigger) { + var method = (trigger && trigger == 'serialize') ? 'saveHTML' : 'destroy'; if (typeof params != 'undefined') { - var instance = YAHOO.widget.EditorInfo.getEditorById(params.field); + var instance = YAHOO.widget.EditorInfo._instances[params.field]; if (instance) { - instance.destroy(); + instance[method](); + if (method == 'destroy') { + delete YAHOO.widget.EditorInfo._instances[params.field]; + } } } else { for (var e in YAHOO.widget.EditorInfo._instances) { // Save contents of all editors back into textareas. var instance = YAHOO.widget.EditorInfo._instances[e]; - instance.destroy(); + instance[method](); + if (method == 'destroy') { + delete YAHOO.widget.EditorInfo._instances[e]; + } } } }; +/** + * Instance methods for YUI Editor. + */ +Drupal.wysiwyg.editor.instance.yui = { + addPlugin: function (plugin, settings, pluginSettings) { + if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') { + return; + } + var editor = YAHOO.widget.EditorInfo.getEditorById(this.field); + var button = editor.toolbar.getButtonByValue(plugin); + $(button._button).parent().css('background', 'transparent url(' + settings.icon + ') no-repeat center'); + // 'this' will reference the toolbar while inside the event handler. + var instanceId = this.field; + editor.toolbar.on(plugin + 'Click', function (e) { + var selectedElement = editor._getSelectedElement(); + // @todo Using .html() will cause XTHML vs HTML conflicts. + var data = { + format: 'html', + node: selectedElement, + content: $(selectedElement).html() + }; + Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instanceId); + }); + }, + + prepareContent: function (content) { + var editor = YAHOO.widget.EditorInfo.getEditorById(this.field); + content = editor.cleanHTML(content); + return content; + }, + + insert: function (content) { + YAHOO.widget.EditorInfo.getEditorById(this.field).cmd_inserthtml(content); + }, + + setContent: function (content) { + YAHOO.widget.EditorInfo.getEditorById(this.field).setEditorHTML(content); + }, + + getContent: function () { + var instance = YAHOO.widget.EditorInfo.getEditorById(this.field); + return instance.cleanHTML(instance.getEditorHTML(content)); + } +}; + })(jQuery); diff --git a/editors/nicedit.inc b/editors/nicedit.inc index 779660c9..6acc800d 100644 --- a/editors/nicedit.inc +++ b/editors/nicedit.inc @@ -77,13 +77,13 @@ function wysiwyg_nicedit_settings($editor, $config, $theme) { // Add editor content stylesheet. if (isset($config['css_setting'])) { if ($config['css_setting'] == 'theme') { - $css = path_to_theme() . '/style.css'; + $css = drupal_get_path('theme', variable_get('theme_default', NULL)) . '/style.css'; if (file_exists($css)) { $settings['externalCSS'] = base_path() . $css; } } - else if ($config['css_setting'] == 'self' && isset($config['css_path'])) { - $settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme())); + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); } } diff --git a/editors/openwysiwyg.inc b/editors/openwysiwyg.inc index f521da56..b3ad84dd 100644 --- a/editors/openwysiwyg.inc +++ b/editors/openwysiwyg.inc @@ -102,8 +102,8 @@ function wysiwyg_openwysiwyg_settings($editor, $config, $theme) { if ($config['css_setting'] == 'theme') { $settings['CSSFile'] = reset(wysiwyg_get_css()); } - else if ($config['css_setting'] == 'self' && isset($config['css_path'])) { - $settings['CSSFile'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme())); + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['CSSFile'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); } } diff --git a/editors/tinymce.inc b/editors/tinymce.inc index 3be6a459..1dbd594c 100644 --- a/editors/tinymce.inc +++ b/editors/tinymce.inc @@ -28,6 +28,7 @@ function wysiwyg_tinymce_editor() { ), 'version callback' => 'wysiwyg_tinymce_version', 'themes callback' => 'wysiwyg_tinymce_themes', + 'init callback' => 'wysiwyg_tinymce_init', 'settings callback' => 'wysiwyg_tinymce_settings', 'plugin callback' => 'wysiwyg_tinymce_plugins', 'plugin settings callback' => 'wysiwyg_tinymce_plugin_settings', @@ -125,6 +126,40 @@ function wysiwyg_tinymce_themes($editor, $profile) { return array('advanced', 'simple'); } +/** + * Returns an initialization JavaScript for this editor library. + * + * @param array $editor + * The editor library definition. + * @param string $library + * The library variant key from $editor['libraries']. + * @param object $profile + * The (first) wysiwyg editor profile. + * + * @return string + * A string containing inline JavaScript to execute before the editor library + * script is loaded. + */ +function wysiwyg_tinymce_init($editor, $library) { + // TinyMCE unconditionally searches for its library filename in SCRIPT tags on + // on the page upon loading the library in order to determine the base path to + // itself. When JavaScript aggregation is enabled, this search fails and all + // relative constructed paths within TinyMCE are broken. The library has a + // tinyMCE.baseURL property, but it is not publicly documented and thus not + // reliable. The official support forum suggests to solve the issue through + // the global window.tinyMCEPreInit variable also used by various serverside + // compressor scrips available from the official website. + // @see http://www.tinymce.com/forum/viewtopic.php?id=23286 + $settings = drupal_json_encode(array( + 'base' => base_path() . $editor['library path'], + 'suffix' => (strpos($library, 'src') !== FALSE || strpos($library, 'dev') !== FALSE ? '_src' : ''), + 'query' => '', + )); + return << base_path(), '%t' => path_to_theme())); + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['content_css'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); } } @@ -232,15 +268,15 @@ function wysiwyg_tinymce_settings($editor, $config, $theme) { $settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $button)] = 1; } // Add external plugins to the list of extensions. - else if ($type == 'buttons' && empty($plugins[$plugin]['internal'])) { + elseif ($type == 'buttons' && empty($plugins[$plugin]['internal'])) { $settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $plugin)] = 1; } // Add internal buttons that also need to be loaded as extension. - else if ($type == 'buttons' && !empty($plugins[$plugin]['load'])) { + elseif ($type == 'buttons' && !empty($plugins[$plugin]['load'])) { $settings['extensions'][$plugin] = 1; } // Add plain extensions. - else if ($type == 'extensions' && !empty($plugins[$plugin]['load'])) { + elseif ($type == 'extensions' && !empty($plugins[$plugin]['load'])) { $settings['extensions'][$plugin] = 1; } // Allow plugins to add valid HTML elements. @@ -268,7 +304,7 @@ function wysiwyg_tinymce_settings($editor, $config, $theme) { $settings += array( 'theme_advanced_resize_horizontal' => FALSE, 'theme_advanced_resizing_use_cookie' => FALSE, - 'theme_advanced_path_location' => isset($config['path_loc']) ? $config['path_loc'] : 'bottom', + 'theme_advanced_statusbar_location' => isset($config['path_loc']) ? $config['path_loc'] : 'bottom', 'theme_advanced_resizing' => isset($config['resizing']) ? $config['resizing'] : 1, 'theme_advanced_toolbar_location' => isset($config['toolbar_loc']) ? $config['toolbar_loc'] : 'top', 'theme_advanced_toolbar_align' => isset($config['toolbar_align']) ? $config['toolbar_align'] : 'left', @@ -361,7 +397,7 @@ function _wysiwyg_tinymce_plugin_name($op, $name) { } return $name; } - else if ($op == 'remove') { + elseif ($op == 'remove') { if (strpos($name, '-') === 0) { return substr($name, 1); } @@ -386,6 +422,8 @@ function wysiwyg_tinymce_plugins($editor) { 'link' => t('Link'), 'unlink' => t('Unlink'), 'anchor' => t('Anchor'), 'image' => t('Image'), 'cleanup' => t('Clean-up'), + 'formatselect' => t('Block format'), 'styleselect' => t('Styles'), + 'fontselect' => t('Font'), 'fontsizeselect' => t('Font size'), 'forecolor' => t('Forecolor'), 'backcolor' => t('Backcolor'), 'sup' => t('Superscript'), 'sub' => t('Subscript'), 'blockquote' => t('Blockquote'), 'code' => t('Source code'), @@ -402,15 +440,15 @@ function wysiwyg_tinymce_plugins($editor) { 'path' => $editor['library path'] . '/plugins/advhr', 'buttons' => array('advhr' => t('Advanced horizontal rule')), 'extended_valid_elements' => array('hr[class|width|size|noshade]'), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advhr', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advhr', 'internal' => TRUE, 'load' => TRUE, ), 'advimage' => array( 'path' => $editor['library path'] . '/plugins/advimage', 'extensions' => array('advimage' => t('Advanced image')), - 'extended_valid_elements' => array('img[src|alt|title|align|width|height|usemap|hspace|vspace|border|style|class|onmouseover|onmouseout|id|name]'), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage', + 'extended_valid_elements' => array('img[src|alt|title|align|width|height|usemap|hspace|vspace|border|style|class|onmouseover|onmouseout|id|name|longdesc]'), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advimage', 'internal' => TRUE, 'load' => TRUE, ), @@ -418,49 +456,42 @@ function wysiwyg_tinymce_plugins($editor) { 'path' => $editor['library path'] . '/plugins/advlink', 'extensions' => array('advlink' => t('Advanced link')), 'extended_valid_elements' => array('a[name|href|target|title|class|onfocus|onblur|onclick|ondlbclick|onmousedown|onmouseup|onmouseover|onmouseout|onkeypress|onkeydown|onkeyup|id|style|rel]'), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlink', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advlink', 'internal' => TRUE, 'load' => TRUE, ), 'autosave' => array( 'path' => $editor['library path'] . '/plugins/autosave', 'extensions' => array('autosave' => t('Auto save')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:autosave', 'internal' => TRUE, 'load' => TRUE, ), 'contextmenu' => array( 'path' => $editor['library path'] . '/plugins/contextmenu', 'extensions' => array('contextmenu' => t('Context menu')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:contextmenu', 'internal' => TRUE, 'load' => TRUE, ), 'directionality' => array( 'path' => $editor['library path'] . '/plugins/directionality', 'buttons' => array('ltr' => t('Left-to-right'), 'rtl' => t('Right-to-left')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/directionality', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:directionality', 'internal' => TRUE, 'load' => TRUE, ), 'emotions' => array( 'path' => $editor['library path'] . '/plugins/emotions', 'buttons' => array('emotions' => t('Emotions')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/emotions', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:emotions', 'internal' => TRUE, 'load' => TRUE, ), - 'font' => array( - 'path' => $editor['library path'] . '/plugins/font', - 'buttons' => array('formatselect' => t('HTML block format'), 'fontselect' => t('Font'), 'fontsizeselect' => t('Font size'), 'styleselect' => t('Font style')), - 'extended_valid_elements' => array('font[face|size|color|style],span[class|align|style]'), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/font', - 'internal' => TRUE, - ), 'fullscreen' => array( 'path' => $editor['library path'] . '/plugins/fullscreen', 'buttons' => array('fullscreen' => t('Fullscreen')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullscreen', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:fullscreen', 'internal' => TRUE, 'load' => TRUE, ), @@ -470,7 +501,7 @@ function wysiwyg_tinymce_plugins($editor) { 'options' => array( 'dialog_type' => array('modal'), ), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:inlinepopups', 'internal' => TRUE, 'load' => TRUE, ), @@ -481,56 +512,56 @@ function wysiwyg_tinymce_plugins($editor) { 'plugin_insertdate_dateFormat' => '%Y-%m-%d', 'plugin_insertdate_timeFormat' => '%H:%M:%S', ), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/insertdatetime', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:insertdatetime', 'internal' => TRUE, 'load' => TRUE, ), 'layer' => array( 'path' => $editor['library path'] . '/plugins/layer', 'buttons' => array('insertlayer' => t('Insert layer'), 'moveforward' => t('Move forward'), 'movebackward' => t('Move backward'), 'absolute' => t('Absolute')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:layer', 'internal' => TRUE, 'load' => TRUE, ), 'paste' => array( 'path' => $editor['library path'] . '/plugins/paste', 'buttons' => array('pastetext' => t('Paste text'), 'pasteword' => t('Paste from Word'), 'selectall' => t('Select all')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:paste', 'internal' => TRUE, 'load' => TRUE, ), 'preview' => array( 'path' => $editor['library path'] . '/plugins/preview', 'buttons' => array('preview' => t('Preview')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/preview', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:preview', 'internal' => TRUE, 'load' => TRUE, ), 'print' => array( 'path' => $editor['library path'] . '/plugins/print', 'buttons' => array('print' => t('Print')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/print', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:print', 'internal' => TRUE, 'load' => TRUE, ), 'searchreplace' => array( 'path' => $editor['library path'] . '/plugins/searchreplace', 'buttons' => array('search' => t('Search'), 'replace' => t('Replace')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:searchreplace', 'internal' => TRUE, 'load' => TRUE, ), 'style' => array( 'path' => $editor['library path'] . '/plugins/style', - 'buttons' => array('styleprops' => t('Style properties')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/style', + 'buttons' => array('styleprops' => t('Advanced CSS styles')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:style', 'internal' => TRUE, 'load' => TRUE, ), 'table' => array( 'path' => $editor['library path'] . '/plugins/table', 'buttons' => array('tablecontrols' => t('Table')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/table', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:table', 'internal' => TRUE, 'load' => TRUE, ), @@ -540,7 +571,6 @@ function wysiwyg_tinymce_plugins($editor) { 'path' => $editor['library path'] . '/plugins/flash', 'buttons' => array('flash' => t('Flash')), 'extended_valid_elements' => array('img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name|obj|param|embed]'), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/flash', 'internal' => TRUE, 'load' => TRUE, ); @@ -549,14 +579,14 @@ function wysiwyg_tinymce_plugins($editor) { $plugins['media'] = array( 'path' => $editor['library path'] . '/plugins/media', 'buttons' => array('media' => t('Media')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/media', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:media', 'internal' => TRUE, 'load' => TRUE, ); $plugins['xhtmlxtras'] = array( 'path' => $editor['library path'] . '/plugins/xhtmlxtras', 'buttons' => array('cite' => t('Citation'), 'del' => t('Deleted'), 'abbr' => t('Abbreviation'), 'acronym' => t('Acronym'), 'ins' => t('Inserted'), 'attribs' => t('HTML attributes')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:xhtmlxtras', 'internal' => TRUE, 'load' => TRUE, ); @@ -565,7 +595,7 @@ function wysiwyg_tinymce_plugins($editor) { $plugins['bbcode'] = array( 'path' => $editor['library path'] . '/plugins/bbcode', 'extensions' => array('bbcode' => t('BBCode')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:bbcode', 'internal' => TRUE, 'load' => TRUE, ); @@ -573,7 +603,6 @@ function wysiwyg_tinymce_plugins($editor) { $plugins['safari'] = array( 'path' => $editor['library path'] . '/plugins/safari', 'extensions' => array('safari' => t('Safari compatibility')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/safari', 'internal' => TRUE, 'load' => TRUE, ); @@ -583,7 +612,7 @@ function wysiwyg_tinymce_plugins($editor) { $plugins['autoresize'] = array( 'path' => $editor['library path'] . '/plugins/autoresize', 'extensions' => array('autoresize' => t('Auto resize')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:autoresize', 'internal' => TRUE, 'load' => TRUE, ); @@ -592,7 +621,7 @@ function wysiwyg_tinymce_plugins($editor) { $plugins['advlist'] = array( 'path' => $editor['library path'] . '/plugins/advlist', 'extensions' => array('advlist' => t('Advanced list')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist', + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:advlist', 'internal' => TRUE, 'load' => TRUE, ); @@ -601,11 +630,24 @@ function wysiwyg_tinymce_plugins($editor) { $plugins['wordcount'] = array( 'path' => $editor['library path'] . '/plugins/wordcount', 'extensions' => array('wordcount' => t('Word count')), - 'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount', 'internal' => TRUE, 'load' => TRUE, ); } + if (version_compare($editor['installed version'], '3.4.1', '>=')) { + $plugins['lists'] = array( + 'path' => $editor['library path'] . 'plugins/lists', + 'extensions' => array('lists' => t('List normalizer')), + 'url' => 'http://www.tinymce.com/wiki.php/Plugin:lists', + 'internal' => TRUE, + 'load' => TRUE, + 'extended_valid_elements' => array( + 'li[class|dir|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title|type|value]', + 'ol[class|compact|dir|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|start|style|title|type]', + 'ul[class|compact|dir|id|lang|onclick|ondblclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|style|title|type]', + ), + ); + } return $plugins; } diff --git a/editors/whizzywig.inc b/editors/whizzywig.inc index d82cc0fe..acfc7de3 100644 --- a/editors/whizzywig.inc +++ b/editors/whizzywig.inc @@ -104,13 +104,13 @@ function wysiwyg_whizzywig_settings($editor, $config, $theme) { // Add editor content stylesheet. if (isset($config['css_setting'])) { if ($config['css_setting'] == 'theme') { - $css = path_to_theme() . '/style.css'; + $css = drupal_get_path('theme', variable_get('theme_default', NULL)) . '/style.css'; if (file_exists($css)) { $settings['externalCSS'] = base_path() . $css; } } - else if ($config['css_setting'] == 'self' && isset($config['css_path'])) { - $settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme())); + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); } } diff --git a/editors/wymeditor.inc b/editors/wymeditor.inc index 3e8ffd24..5f44d641 100644 --- a/editors/wymeditor.inc +++ b/editors/wymeditor.inc @@ -172,10 +172,11 @@ function wysiwyg_wymeditor_settings($editor, $config, $theme) { if (isset($config['css_setting'])) { if ($config['css_setting'] == 'theme') { // WYMeditor only supports one CSS file currently. - $settings['stylesheet'] = reset(wysiwyg_get_css()); + $css = wysiwyg_get_css(); + $settings['stylesheet'] = reset($css); } - else if ($config['css_setting'] == 'self' && isset($config['css_path'])) { - $settings['stylesheet'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme())); + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['stylesheet'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); } } @@ -190,7 +191,7 @@ function wysiwyg_wymeditor_plugins($editor) { 'default' => array( 'buttons' => array( 'Bold' => t('Bold'), 'Italic' => t('Italic'), - 'InsertOrderedList' => t('Bullet list'), 'InsertUnorderedList' => t('Numbered list'), + 'InsertOrderedList' => t('Numbered list'), 'InsertUnorderedList' => t('Bullet list'), 'Outdent' => t('Outdent'), 'Indent' => t('Indent'), 'Undo' => t('Undo'), 'Redo' => t('Redo'), 'CreateLink' => t('Link'), 'Unlink' => t('Unlink'), @@ -212,22 +213,22 @@ function wysiwyg_wymeditor_plugins($editor) { */ function _wysiwyg_wymeditor_button_info() { return array( - 'Bold' => array('title'=> 'Strong', 'css'=> 'wym_tools_strong'), - 'Italic' => array('title'=> 'Emphasis', 'css'=> 'wym_tools_emphasis'), - 'Superscript' => array('title'=> 'Superscript', 'css'=> 'wym_tools_superscript'), - 'Subscript' => array('title'=> 'Subscript', 'css'=> 'wym_tools_subscript'), - 'InsertOrderedList' => array('title'=> 'Ordered_List', 'css'=> 'wym_tools_ordered_list'), - 'InsertUnorderedList' => array('title'=> 'Unordered_List', 'css'=> 'wym_tools_unordered_list'), - 'Indent' => array('title'=> 'Indent', 'css'=> 'wym_tools_indent'), - 'Outdent' => array('title'=> 'Outdent', 'css'=> 'wym_tools_outdent'), - 'Undo' => array('title'=> 'Undo', 'css'=> 'wym_tools_undo'), - 'Redo' => array('title'=> 'Redo', 'css'=> 'wym_tools_redo'), - 'CreateLink' => array('title'=> 'Link', 'css'=> 'wym_tools_link'), - 'Unlink' => array('title'=> 'Unlink', 'css'=> 'wym_tools_unlink'), - 'InsertImage' => array('title'=> 'Image', 'css'=> 'wym_tools_image'), - 'InsertTable' => array('title'=> 'Table', 'css'=> 'wym_tools_table'), - 'Paste' => array('title'=> 'Paste_From_Word', 'css'=> 'wym_tools_paste'), - 'ToggleHtml' => array('title'=> 'HTML', 'css'=> 'wym_tools_html'), - 'Preview' => array('title'=> 'Preview', 'css'=> 'wym_tools_preview'), + 'Bold' => array('title' => 'Strong', 'css' => 'wym_tools_strong'), + 'Italic' => array('title' => 'Emphasis', 'css' => 'wym_tools_emphasis'), + 'Superscript' => array('title' => 'Superscript', 'css' => 'wym_tools_superscript'), + 'Subscript' => array('title' => 'Subscript', 'css' => 'wym_tools_subscript'), + 'InsertOrderedList' => array('title' => 'Ordered_List', 'css' => 'wym_tools_ordered_list'), + 'InsertUnorderedList' => array('title' => 'Unordered_List', 'css' => 'wym_tools_unordered_list'), + 'Indent' => array('title' => 'Indent', 'css' => 'wym_tools_indent'), + 'Outdent' => array('title' => 'Outdent', 'css' => 'wym_tools_outdent'), + 'Undo' => array('title' => 'Undo', 'css' => 'wym_tools_undo'), + 'Redo' => array('title' => 'Redo', 'css' => 'wym_tools_redo'), + 'CreateLink' => array('title' => 'Link', 'css' => 'wym_tools_link'), + 'Unlink' => array('title' => 'Unlink', 'css' => 'wym_tools_unlink'), + 'InsertImage' => array('title' => 'Image', 'css' => 'wym_tools_image'), + 'InsertTable' => array('title' => 'Table', 'css' => 'wym_tools_table'), + 'Paste' => array('title' => 'Paste_From_Word', 'css' => 'wym_tools_paste'), + 'ToggleHtml' => array('title' => 'HTML', 'css' => 'wym_tools_html'), + 'Preview' => array('title' => 'Preview', 'css' => 'wym_tools_preview'), ); } diff --git a/editors/yui.inc b/editors/yui.inc index 7e3c697c..36d0a596 100644 --- a/editors/yui.inc +++ b/editors/yui.inc @@ -45,6 +45,14 @@ function wysiwyg_yui_editor() { 'load callback' => 'wysiwyg_yui_load', 'settings callback' => 'wysiwyg_yui_settings', 'plugin callback' => 'wysiwyg_yui_plugins', + 'plugin settings callback' => 'wysiwyg_yui_plugin_settings', + 'proxy plugin' => array( + 'drupal' => array( + 'load' => TRUE, + 'proxy' => TRUE, + ), + ), + 'proxy plugin settings callback' => 'wysiwyg_yui_proxy_plugin_settings', 'versions' => array( '2.7.0' => array( 'js files' => array('yui.js'), @@ -177,7 +185,7 @@ function wysiwyg_yui_settings($editor, $config, $theme) { } } } - else if ($button == 'fontname') { + elseif ($button == 'fontname') { $extra = array('menu' => array( array('text' => 'Arial', 'checked' => TRUE), array('text' => 'Arial Black'), @@ -202,8 +210,8 @@ function wysiwyg_yui_settings($editor, $config, $theme) { if ($config['css_setting'] == 'theme') { $settings['extracss'] = wysiwyg_get_css(); } - else if ($config['css_setting'] == 'self' && isset($config['css_path'])) { - $settings['extracss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme())); + elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['extracss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => drupal_get_path('theme', variable_get('theme_default', NULL)))); $settings['extracss'] = explode(',', $settings['extracss']); } // YUI only supports inline CSS, so we need to use @import directives. @@ -233,8 +241,7 @@ function wysiwyg_yui_button_setting($editor, $plugin, $button, $extra = array()) static $plugins; if (!isset($plugins)) { - // @todo Invoke all enabled plugins, not just internals. - $plugins = wysiwyg_yui_plugins($editor); + $plugins = wysiwyg_get_plugins($editor['name']); } // Return a simple separator. @@ -269,6 +276,41 @@ function wysiwyg_yui_button_setting($editor, $plugin, $button, $extra = array()) return $button; } +/** + * Build a JS settings array of native external plugins that need to be loaded separately. + */ +function wysiwyg_yui_plugin_settings($editor, $profile, $plugins) { + $settings = array(); + foreach ($plugins as $name => $plugin) { + if (!empty($plugin['load'])) { + // Add path for native external plugins; internal ones are loaded + // automatically. + if (empty($plugin['internal']) && isset($plugin['path'])) { + $settings[$name] = base_path() . $plugin['path']; + } + } + } + return $settings; +} + +/** + * Build a JS settings array for Drupal plugins loaded via the proxy plugin. + */ +function wysiwyg_yui_proxy_plugin_settings($editor, $profile, $plugins) { + $settings = array(); + foreach ($plugins as $name => $plugin) { + // Populate required plugin settings. + $settings[$name] = $plugin['dialog settings'] + array( + 'title' => $plugin['title'], + 'icon' => base_path() . $plugin['icon path'] . '/' . $plugin['icon file'], + 'iconTitle' => $plugin['icon title'], + // @todo These should only be set if the plugin defined them. + 'css' => base_path() . $plugin['css path'] . '/' . $plugin['css file'], + ); + } + return $settings; +} + /** * Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin(). */ diff --git a/tests/wysiwyg_test.info b/tests/wysiwyg_test.info index 1314897d..feee1244 100644 --- a/tests/wysiwyg_test.info +++ b/tests/wysiwyg_test.info @@ -6,9 +6,9 @@ hidden = TRUE dependencies[] = wysiwyg files[] = wysiwyg_test.module -; Information added by drupal.org packaging script on 2011-06-19 -version = "7.x-2.1" +; Information added by drupal.org packaging script on 2012-10-02 +version = "7.x-2.2" core = "7.x" project = "wysiwyg" -datestamp = "1308450722" +datestamp = "1349213776" diff --git a/tests/wysiwyg_test.module b/tests/wysiwyg_test.module index c908e29c..c2dd9a91 100644 --- a/tests/wysiwyg_test.module +++ b/tests/wysiwyg_test.module @@ -5,3 +5,46 @@ * Testing functionality for Wysiwyg module. */ +/** + * Implements hook_menu(). + */ +function wysiwyg_test_menu() { + $items['wysiwyg-test/ajax'] = array( + 'title' => 'Ajaxified form', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('wysiwyg_test_ajax_form'), + 'access callback' => TRUE, + ); + return $items; +} + +/** + * Form constructor for an ajaxified form lazy-loading a textarea. + */ +function wysiwyg_test_ajax_form($form, &$form_state) { + $form['enable'] = array( + '#type' => 'checkbox', + '#title' => 'Load textarea', + '#ajax' => array( + 'callback' => 'wysiwyg_test_ajax_form_callback', + 'wrapper' => 'ajax-wrapper', + ), + ); + $form['wrapper'] = array( + '#type' => 'container', + '#id' => 'ajax-wrapper', + ); + return $form; +} + +/** + * #ajax callback for wysiwyg_test_ajax_form(). + */ +function wysiwyg_test_ajax_form_callback($form, &$form_state) { + $form['body'] = array( + '#type' => 'text_format', + '#default_value' => '', + ); + form_builder($form['form_id']['#value'], $form, $form_state); + return $form['body']; +} diff --git a/wysiwyg-dialog-page.tpl.php b/wysiwyg-dialog-page.tpl.php index 0811ae78..3e77d79b 100644 --- a/wysiwyg-dialog-page.tpl.php +++ b/wysiwyg-dialog-page.tpl.php @@ -2,83 +2,27 @@ /** * @file - * Theme template to display a single Wysiwyg (plugin) dialog page. - * - * Available variables: - * - * General utility variables: - * - $base_path: The base URL path of the Drupal installation. At the very - * least, this will always default to /. - * - $css: An array of CSS files for the current page. - * - $directory: The directory the theme is located in, e.g. themes/garland or - * themes/garland/minelli. - * - $logged_in: TRUE if the user is registered and signed in. - * - $is_admin: TRUE if the user has permission to access administration pages. - * - * Page metadata: - * - $language: (object) The language the site is being displayed in. - * $language->language contains its textual representation. - * $language->dir contains the language direction. It will either be 'ltr' or 'rtl'. - * - $head_title: A modified version of the page title, for use in the TITLE tag. - * - $head: Markup for the HEAD section (including meta tags, keyword tags, and - * so on). - * - $styles: Style tags necessary to import all CSS files for the page. - * - $scripts: Script tags necessary to load the JavaScript files and settings - * for the page. - * - * Site identity: - * - $site_name: The name of the site, empty when display has been disabled - * in theme settings. - * - * Page content (in order of occurrance in the default page.tpl.php): - * - $breadcrumb: The breadcrumb trail for the current page. - * - $title: The page title, for use in the actual HTML content. - * - $help: Dynamic help text, mostly for admin pages. - * - $messages: HTML for status and error messages. Should be displayed prominently. - * - $tabs: Tabs linking to any sub-pages beneath the current page (e.g., the view - * and edit tabs when displaying a node). - * - * - $content: The main content of the current Drupal page. - * - * Footer/closing data: - * - $footer : The footer region. - * - $closure: Final closing markup from any modules that have altered the page. - * This variable should always be output last, after all other dynamic content. - * - * @see template_preprocess() - * @see template_preprocess_wysiwyg_dialog_page() + * Theme implementation to display a single Wysiwyg (plugin) dialog page. */ ?> - - - - <?php print $head_title; ?> - - - - - -
-
-
- -
-

-
- - -
- -
-
+ -
-
-
- - - +
+ +
+ + +

+ +
+ + + +
+ +
+ +
diff --git a/wysiwyg.admin.inc b/wysiwyg.admin.inc index 510e1e31..497e5d4c 100644 --- a/wysiwyg.admin.inc +++ b/wysiwyg.admin.inc @@ -135,21 +135,22 @@ function wysiwyg_profile_form($form, &$form_state, $profile) { } $icon = file_exists($img_src) ? '' : ''; } - $title = (isset($meta['url']) ? l($title, $meta['url'], array('target' => '_blank')) : $title); - $title = (!empty($icon) ? $icon . ' ' . $title : $title); + $title = (!empty($icon) ? $icon . ' ' . check_plain($title) : check_plain($title)); $form['buttons'][$name][$button] = array( '#type' => 'checkbox', '#title' => $title, '#default_value' => !empty($profile->settings['buttons'][$name][$button]) ? $profile->settings['buttons'][$name][$button] : FALSE, + '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL, ); } } - else if (isset($meta['extensions']) && is_array($meta['extensions'])) { + elseif (isset($meta['extensions']) && is_array($meta['extensions'])) { foreach ($meta['extensions'] as $extension => $title) { $form['buttons'][$name][$extension] = array( '#type' => 'checkbox', - '#title' => isset($meta['url']) ? l($title, $meta['url'], array('target' => '_blank')) : $title, + '#title' => check_plain($title), '#default_value' => !empty($profile->settings['buttons'][$name][$extension]) ? $profile->settings['buttons'][$name][$extension] : FALSE, + '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL, ); } } @@ -299,6 +300,19 @@ function wysiwyg_profile_form($form, &$form_state, $profile) { '#weight' => 110, ); + // Supply contextual information for other callbacks and handlers. + // @todo Modernize this form for D7+ and declare these earlier. + // $profile is the primary object of this form, and as an entity, usually + // expected to live in $form_state[$entity_type]. + $form_state['wysiwyg_profile'] = $profile; + $form_state['wysiwyg']['editor'] = $editor; + $form_state['wysiwyg']['plugins'] = $plugins; + + // Allow editor library specific changes to be made to the form. + if (isset($editor['settings form callback'])) { + $editor['settings form callback']($form, $form_state); + } + return $form; } @@ -363,14 +377,11 @@ function theme_wysiwyg_admin_button_table($variables) { // Split checkboxes into rows with 3 columns. $total = count($buttons); $rows = array(); - for ($i = 0; $i < $total; $i++) { + for ($i = 0; $i < $total; $i += 3) { $row = array(); - $row[] = array('data' => $buttons[$i]); - if (isset($buttons[++$i])) { - $row[] = array('data' => $buttons[$i]); - } - if (isset($buttons[++$i])) { - $row[] = array('data' => $buttons[$i]); + $row_buttons = array_slice($buttons, $i, 3) + array_fill(0, 3, array()); + foreach ($row_buttons as $row_button) { + $row[] = array('data' => $row_button); } $rows[] = $row; } @@ -416,6 +427,11 @@ function wysiwyg_profile_overview($form, &$form_state) { $instructions = '

' . t('Extract the archive and copy its contents into a new folder in the following location:
@editor-path', $targs) . '

'; $instructions .= '

' . t('So the actual library can be found at:
@library-filepath', $targs) . '

'; + // Add any install notes. + if (!empty($editor['install note callback']) && function_exists($editor['install note callback'])) { + $instructions .= '
' . $editor['install note callback']() . '
'; + } + $status[$name]['description'] .= $instructions; $count--; } @@ -457,9 +473,20 @@ function wysiwyg_profile_overview($form, &$form_state) { // Only display editor selection for associated input formats to avoid // confusion about disabled selection. if (isset($profiles[$id]) && !empty($profiles[$id]->editor)) { + $editor_name = $profiles[$id]->editor; + $installed = !empty($editors[$editor_name]['installed']); $form['formats'][$id]['editor'] = array( - '#markup' => $options[$profiles[$id]->editor], + '#wysiwyg-editor-name' => $editor_name, ); + if ($installed) { + $form['formats'][$id]['editor']['#markup'] = $options[$editor_name]; + } + else { + drupal_set_message(t('Missing %editor library for %format format. Re-install the %editor library or delete the editor profile.', array( + '%editor' => $editors[$editor_name]['title'], + '%format' => $format->name, + )), 'warning'); + } } else { $form['formats'][$id]['editor'] = array( @@ -494,17 +521,28 @@ function theme_wysiwyg_profile_overview($variables) { if (!isset($form['formats'])) { return; } + $editors = wysiwyg_get_all_editors(); $output = ''; - $header = array(t('Input format'), t('Editor'), array('data' => t('Operations'), 'colspan' => 2)); + $header = array(t('Text format'), t('Editor'), array('data' => t('Operations'), 'colspan' => 2)); $rows = array(); foreach (element_children($form['formats']) as $item) { $format = &$form['formats'][$item]; - $rows[] = array( - drupal_render($format['name']), - drupal_render($format['editor']), - isset($format['edit']) ? drupal_render($format['edit']) : '', - isset($format['delete']) ? drupal_render($format['delete']) : '', + $row = array( + 'data' => array( + drupal_render($format['name']), + drupal_render($format['editor']), + isset($format['edit']) ? drupal_render($format['edit']) : '', + isset($format['delete']) ? drupal_render($format['delete']) : '', + ), ); + if (empty($row['data'][1])) { + $row['data'][1] = array( + 'data' => t('Missing library: @library', array('@library' => $editors[$format['editor']['#wysiwyg-editor-name']]['title'])), + 'class' => 'error', + ); + $row['class'] = array('error'); + } + $rows[] = $row; } $form['formats']['table']['#markup'] = theme('table', array('header' => $header, 'rows' => $rows)); $output .= drupal_render_children($form); @@ -554,4 +592,3 @@ function wysiwyg_profile_delete_confirm_submit($form, &$form_state) { drupal_set_message(t('Wysiwyg profile for %name has been deleted.', array('%name' => $format->name))); $form_state['redirect'] = 'admin/config/content/wysiwyg'; } - diff --git a/wysiwyg.api.php b/wysiwyg.api.php index 5f5b8390..c4d88579 100644 --- a/wysiwyg.api.php +++ b/wysiwyg.api.php @@ -172,6 +172,83 @@ function hook_INCLUDE_plugin() { return $plugins; } +/** + * Define a Wysiwyg editor library. + * + * @todo Complete this documentation. + */ +function hook_INCLUDE_editor() { + $editor['ckeditor'] = array( + // The official, human-readable label of the editor library. + 'title' => 'CKEditor', + // The URL to the library's homepage. + 'vendor url' => 'http://ckeditor.com', + // The URL to the library's download page. + 'download url' => 'http://ckeditor.com/download', + // A definition of available variants for the editor library. + // The first defined is used by default. + 'libraries' => array( + '' => array( + 'title' => 'Default', + 'files' => array( + 'ckeditor.js' => array('preprocess' => FALSE), + ), + ), + 'src' => array( + 'title' => 'Source', + 'files' => array( + 'ckeditor_source.js' => array('preprocess' => FALSE), + ), + ), + ), + // (optional) A callback to invoke to return additional notes for installing + // the editor library in the administrative list/overview. + 'install note callback' => 'wysiwyg_ckeditor_install_note', + // A callback to determine the library's version. + 'version callback' => 'wysiwyg_ckeditor_version', + // A callback to return available themes/skins for the editor library. + 'themes callback' => 'wysiwyg_ckeditor_themes', + // (optional) A callback to perform editor-specific adjustments or + // enhancements for the administrative editor profile settings form. + 'settings form callback' => 'wysiwyg_ckeditor_settings_form', + // (optional) A callback to return an initialization JavaScript snippet for + // this editor library, loaded before the actual library files. The returned + // JavaScript is executed as inline script in a primitive environment, + // before the DOM is loaded; typically used to prime a base path and other + // global window variables for the editor library before it is loaded. + // All implementations should verbosely document what they are doing and + // why that is required. + 'init callback' => 'wysiwyg_ckeditor_init', + // A callback to convert administrative profile/editor settings into + // JavaScript settings. + 'settings callback' => 'wysiwyg_ckeditor_settings', + // A callback to supply definitions of available editor plugins. + 'plugin callback' => 'wysiwyg_ckeditor_plugins', + // A callback to convert administrative plugin settings for a editor profile + // into JavaScript settings. + 'plugin settings callback' => 'wysiwyg_ckeditor_plugin_settings', + // (optional) Defines the proxy plugin that handles plugins provided by + // Drupal modules, which work in all editors that support proxy plugins. + 'proxy plugin' => array( + 'drupal' => array( + 'load' => TRUE, + 'proxy' => TRUE, + ), + ), + // (optional) A callback to convert proxy plugin settings into JavaScript + // settings. + 'proxy plugin settings callback' => 'wysiwyg_ckeditor_proxy_plugin_settings', + // Defines the list of supported (minimum) versions of the editor library, + // and the respective Drupal integration files to load. + 'versions' => array( + '3.0.0.3665' => array( + 'js files' => array('ckeditor-3.0.js'), + ), + ), + ); + return $editor; +} + /** * Act on editor profile settings. * diff --git a/wysiwyg.dialog.inc b/wysiwyg.dialog.inc index 500f78d7..c296d2e0 100644 --- a/wysiwyg.dialog.inc +++ b/wysiwyg.dialog.inc @@ -6,7 +6,68 @@ */ /** - * Menu callback; Output a wysiwyg plugin dialog page. + * Page callback; Outputs a dialog page for a wysiwyg plugin. + * + * A Wysiwyg dialog is a bare minimum, simple HTML page; presented in a + * modal/popup window, triggered via JavaScript. + * + * However, Drupal core does not support such a concept, at all. + * Insanity happens on two separate layers: + * - All HTML pages go through the default delivery callback of + * drupal_deliver_html_page(), which calls into drupal_render_page(), which + * in turn *unconditionally* invokes hook_page_build() implementations. Thus, + * block_page_build() and similar implementations add the entirety of their + * page regions and blocks to our simple dialog page. + * Obviously, we don't want that. + * - There is a nice default 'page' theme template implementation, which + * performs all the heavy-lifting that is required for outputting a sane HTML + * page through preprocess and process functions. The theme system does not + * support to "inherit" preprocess and process hooks to alternative + * implementations. Even a very basic HTML page requires almost all of that. + * However, the default page template (normally overridden by a theme) + * contains too many regions and usually also huge a header and footer. + * Obviously, we don't want that. + * + * The poor workaround would be to follow the Overlay module's implementation in + * core: override the theme, build everything, and after doing all of that, + * strip away what isn't needed. Obviously, we don't want that. + * + * Instead, we bend Drupal to sane rules: + * - This page callback returns the actual main content. + * - wysiwyg_menu() defines a custom delivery callback that replaces + * drupal_deliver_html_page(), just because we need to replace + * drupal_render_page(). + * - Our replacement for drupal_render_page() builds a $page that does not use + * #type 'page' but #type 'wysiwyg_dialog_page' instead. + * - #type 'wysiwyg_dialog_page' is defined like #type 'page' in + * system_element_info(), but is required, because there's no way to inherit + * a theme definition but override the page template file to be used. + * - As a consequence, #type 'wysiwyg_dialog_page' uses + * #theme 'wysiwyg_dialog_page', for which we have to implement stub + * preprocess and process callbacks in order to call into the ones for + * #theme 'page'. + * + * As a result we get: + * - A HTML response. + * - A HTML page wrapped into html.tpl.php. + * - A page title, title prefix/suffix, messages, help, etc.pp. + * - A simple page without regions and blocks (neither built nor rendered). + * + * @see wysiwyg_menu() + * @see wysiwyg_deliver_dialog_page + * @see wysiwyg_render_dialog_page() + * @see wysiwyg_element_info() + * @see wysiwyg_theme() + * @see template_preprocess_wysiwyg_dialog_page() + * @see template_process_wysiwyg_dialog_page() + * + * @see drupal_deliver_page() + * @see drupal_deliver_html_page() + * @see drupal_render_page() + * @see system_element_info() + * @see drupal_common_theme() + * @see template_preprocess_page() + * @see template_process_page() */ function wysiwyg_dialog($plugin, $instance) { $plugins = wysiwyg_get_all_plugins(); @@ -27,7 +88,74 @@ function wysiwyg_dialog($plugin, $instance) { ); drupal_add_js(array('wysiwyg' => $settings), 'setting'); - echo theme('wysiwyg_dialog_page', $callback($instance)); + $build = $callback($instance); + if (!is_array($build)) { + $build = array('#markup' => $build); + } + $build += array( + '#instance' => $instance, + '#plugin' => $plugin, + ); + return $build; +} + +/** + * @see drupal_deliver_html_page() + */ +function wysiwyg_deliver_dialog_page($page_callback_result) { + // Menu status constants are integers; page content is a string or array. + if (is_int($page_callback_result)) { + return drupal_deliver_html_page($page_callback_result); + } + + // Emit the correct charset HTTP header, but not if the page callback + // result is NULL, since that likely indicates that it printed something + // in which case, no further headers may be sent, and not if code running + // for this page request has already set the content type header. + if (isset($page_callback_result) && is_null(drupal_get_http_header('Content-Type'))) { + drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); + } + + // Send appropriate HTTP-Header for browsers and search engines. + global $language; + drupal_add_http_header('Content-Language', $language->language); + + if (isset($page_callback_result)) { + // Print anything besides a menu constant, assuming it's not NULL or + // undefined. + print wysiwyg_render_dialog_page($page_callback_result); + } + + // Perform end-of-request tasks. + drupal_page_footer(); +} + +/** + * @see drupal_render_page() + */ +function wysiwyg_render_dialog_page($page) { + $main_content_display = &drupal_static('system_main_content_added', FALSE); + + // Allow menu callbacks to return strings or arbitrary arrays to render. + // If the array returned is not of #type page directly, we need to fill + // in the page with defaults. + if (is_string($page) || (is_array($page) && (!isset($page['#type']) || ($page['#type'] != 'page')))) { + drupal_set_page_content($page); + $page = element_info('wysiwyg_dialog_page'); + } + + // Modules alter the $page as needed. Blocks are populated into regions like + // 'sidebar_first', 'footer', etc. + drupal_alter(array('wysiwyg_dialog_page', 'page'), $page); + + // If no module has taken care of the main content, add it to the page now. + // This allows the site to still be usable even if no modules that + // control page regions (for example, the Block module) are enabled. + if (!$main_content_display) { + $page['content']['system_main'] = drupal_set_page_content(); + } + + return drupal_render($page); } /** @@ -35,29 +163,21 @@ function wysiwyg_dialog($plugin, $instance) { * * @see wysiwyg_dialog() * @see wysiwyg-dialog-page.tpl.php - * @see template_preprocess() + * @see template_preprocess_page() */ function template_preprocess_wysiwyg_dialog_page(&$variables) { - // Construct page title - $head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal')); - - $variables['head_title'] = implode(' | ', $head_title); - $variables['base_path'] = base_path(); - $variables['front_page'] = url(); - // @todo Would a breadcrumb make sense / possible at all? - // $variables['breadcrumb'] = theme('breadcrumb', drupal_get_breadcrumb()); - $variables['head'] = drupal_get_html_head(); - $variables['help'] = theme('help'); - $variables['language'] = $GLOBALS['language']; - $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr'; - $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; - $variables['site_name'] = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : ''); - $variables['css'] = drupal_add_css(); - $variables['styles'] = drupal_get_css(); - $variables['scripts'] = drupal_get_js(); - $variables['tabs'] = theme('menu_local_tasks'); - $variables['title'] = drupal_get_title(); - // Closure should be filled last. - $variables['closure'] = theme('closure'); + template_preprocess_page($variables); +} + + +/** + * Template process function for theme_wysiwyg_dialog_page(). + * + * @see wysiwyg_dialog() + * @see wysiwyg-dialog-page.tpl.php + * @see template_process_page() + */ +function template_process_wysiwyg_dialog_page(&$variables) { + template_process_page($variables); } diff --git a/wysiwyg.info b/wysiwyg.info index 840817ae..5f70c0cb 100644 --- a/wysiwyg.info +++ b/wysiwyg.info @@ -9,9 +9,9 @@ configure = admin/config/content/wysiwyg files[] = wysiwyg.module files[] = tests/wysiwyg.test -; Information added by drupal.org packaging script on 2011-06-19 -version = "7.x-2.1" +; Information added by drupal.org packaging script on 2012-10-02 +version = "7.x-2.2" core = "7.x" project = "wysiwyg" -datestamp = "1308450722" +datestamp = "1349213776" diff --git a/wysiwyg.install b/wysiwyg.install index 038ba46b..e5dd046d 100644 --- a/wysiwyg.install +++ b/wysiwyg.install @@ -30,6 +30,7 @@ function wysiwyg_schema() { 'description' => 'Configuration settings for the editor.', 'type' => 'text', 'size' => 'normal', + 'serialize' => TRUE, ), ), 'primary key' => array('format'), diff --git a/wysiwyg.js b/wysiwyg.js index 72ca1569..29e2c54b 100644 --- a/wysiwyg.js +++ b/wysiwyg.js @@ -11,7 +11,6 @@ Drupal.wysiwygInit = function() { if (/KDE/.test(navigator.vendor)) { return; } - jQuery.each(Drupal.wysiwyg.editor.init, function(editor) { // Clone, so original settings are not overwritten. this(jQuery.extend(true, {}, Drupal.settings.wysiwyg.configs[editor])); @@ -38,13 +37,13 @@ Drupal.wysiwygInit = function() { * A DOM element, supplied by Drupal.attachBehaviors(). */ Drupal.behaviors.attachWysiwyg = { - attach: function(context, settings) { + attach: function (context, settings) { // This breaks in Konqueror. Prevent it from running. if (/KDE/.test(navigator.vendor)) { return; } - $('.wysiwyg', context).once('wysiwyg', function() { + $('.wysiwyg', context).once('wysiwyg', function () { if (!this.id || typeof Drupal.settings.wysiwyg.triggers[this.id] === 'undefined') { return; } @@ -76,9 +75,27 @@ Drupal.behaviors.attachWysiwyg = { if (event.isDefaultPrevented()) { return; } - Drupal.wysiwygDetach(context, params[format]); + Drupal.wysiwygDetach(context, params[format], 'serialize'); }); }); + }, + + detach: function (context, settings, trigger) { + var wysiwygs; + // The 'serialize' trigger indicates that we should simply update the + // underlying element with the new text, without destroying the editor. + if (trigger == 'serialize') { + // Removing the wysiwyg-processed class guarantees that the editor will + // be reattached. Only do this if we're planning to destroy the editor. + wysiwygs = $('.wysiwyg-processed', context); + } + else { + wysiwygs = $('.wysiwyg', context).removeOnce('wysiwyg'); + } + wysiwygs.each(function () { + var params = Drupal.settings.wysiwyg.triggers[this.id]; + Drupal.wysiwygDetach(context, params, trigger); + }); } }; @@ -136,11 +153,20 @@ Drupal.wysiwygAttach = function(context, params) { * A DOM element, supplied by Drupal.attachBehaviors(). * @param params * An object containing input format parameters. + * @param trigger + * A string describing what is causing the editor to be detached. + * + * @see Drupal.detachBehaviors */ -Drupal.wysiwygDetach = function(context, params) { +Drupal.wysiwygDetach = function (context, params, trigger) { + // Do not attempt to detach an unknown editor instance (Ajax). + if (typeof Drupal.wysiwyg.instances[params.field] == 'undefined') { + return; + } + trigger = trigger || 'unload'; var editor = Drupal.wysiwyg.instances[params.field].editor; if (jQuery.isFunction(Drupal.wysiwyg.editor.detach[editor])) { - Drupal.wysiwyg.editor.detach[editor](context, params); + Drupal.wysiwyg.editor.detach[editor](context, params, trigger); } }; @@ -187,6 +213,7 @@ Drupal.wysiwyg.toggleWysiwyg = function (event) { Drupal.wysiwyg.editor.attach.none(context, params); Drupal.wysiwyg.instances[params.field] = Drupal.wysiwyg.editor.instance.none; Drupal.wysiwyg.instances[params.field].editor = 'none'; + Drupal.wysiwyg.instances[params.field].field = params.field; $(this).html(Drupal.settings.wysiwyg.enable).blur(); } else { @@ -234,4 +261,9 @@ Drupal.wysiwyg.getParams = function(element, params) { */ Drupal.wysiwygInit(); +// Respond to CTools detach behaviors event. +$(document).bind('CToolsDetachBehaviors', function(event, context) { + Drupal.behaviors.attachWysiwyg.detach(context, {}, 'unload'); +}); + })(jQuery); diff --git a/wysiwyg.module b/wysiwyg.module index 771cbd79..22130eab 100644 --- a/wysiwyg.module +++ b/wysiwyg.module @@ -79,9 +79,11 @@ function wysiwyg_menu() { 'type' => MENU_LOCAL_TASK, 'weight' => 10, ); + // @see wysiwyg_dialog() $items['wysiwyg/%'] = array( 'page callback' => 'wysiwyg_dialog', 'page arguments' => array(1), + 'delivery callback' => 'wysiwyg_deliver_dialog_page', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, 'file' => 'wysiwyg.dialog.inc', @@ -89,6 +91,19 @@ function wysiwyg_menu() { return $items; } +/** + * Implements hook_element_info(). + */ +function wysiwyg_element_info() { + // @see wysiwyg_dialog() + $types['wysiwyg_dialog_page'] = array( + '#theme' => 'wysiwyg_dialog_page', + '#theme_wrappers' => array('html'), + '#show_messages' => TRUE, + ); + return $types; +} + /** * Implementation of hook_theme(). * @@ -103,8 +118,9 @@ function wysiwyg_theme() { 'wysiwyg_admin_button_table' => array( 'render element' => 'form', ), + // @see wysiwyg_dialog() 'wysiwyg_dialog_page' => array( - 'variables' => array('content' => NULL, 'show_messages' => TRUE), + 'render element' => 'page', 'file' => 'wysiwyg.dialog.inc', 'template' => 'wysiwyg-dialog-page', ), @@ -117,7 +133,7 @@ function wysiwyg_theme() { function wysiwyg_help($path, $arg) { switch ($path) { case 'admin/config/content/wysiwyg': - $output = '

' . t('A Wysiwyg profile is associated with an input format. A Wysiwyg profile defines which client-side editor is loaded with a particular input format, what buttons or themes are enabled for the editor, how the editor is displayed, and a few other editor-specific functions.') . '

'; + $output = '

' . t('A Wysiwyg profile is associated with a text format. A Wysiwyg profile defines which client-side editor is loaded with a particular text format, what buttons or themes are enabled for the editor, how the editor is displayed, and a few other editor-specific functions.') . '

'; return $output; } } @@ -286,6 +302,7 @@ function wysiwyg_get_profile($format) { function wysiwyg_load_editor($profile) { static $settings_added; static $loaded = array(); + $path = drupal_get_path('module', 'wysiwyg'); $name = $profile->editor; // Library files must be loaded only once. @@ -293,6 +310,13 @@ function wysiwyg_load_editor($profile) { // Load editor. $editor = wysiwyg_get_editor($name); if ($editor) { + $default_library_options = array( + 'type' => 'file', + 'scope' => 'header', + 'defer' => FALSE, + 'cache' => TRUE, + 'preprocess' => TRUE, + ); // Determine library files to load. // @todo Allow to configure the library/execMode to use. if (isset($profile->settings['library']) && isset($editor['libraries'][$profile->settings['library']])) { @@ -305,9 +329,33 @@ function wysiwyg_load_editor($profile) { $files = array_shift($editor['libraries']); $files = $files['files']; } + + // Check whether the editor requires an initialization script. + if (!empty($editor['init callback'])) { + $init = $editor['init callback']($editor, $library, $profile); + if (!empty($init)) { + // Build a file for each of the editors to hold the init scripts. + // @todo Aggregate all initialization scripts into one file. + $uri = 'public://js/wysiwyg/wysiwyg_' . $name . '_' . drupal_hash_base64($init) . '.js'; + $init_exists = file_exists($uri); + if (!$init_exists) { + $js_path = dirname($uri); + file_prepare_directory($js_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + } + // Attempt to create the file, or fall back to an inline script (which + // will not work in Ajax calls). + if (!$init_exists && !file_unmanaged_save_data($init, $uri, FILE_EXISTS_REPLACE)) { + drupal_add_js($init, array('type' => 'inline') + $default_library_options); + } + else { + drupal_add_js(file_create_url($uri), $default_library_options); + } + } + } + foreach ($files as $file => $options) { if (is_array($options)) { - $options += array('type' => 'file', 'scope' => 'header', 'defer' => FALSE, 'cache' => TRUE, 'preprocess' => TRUE); + $options += $default_library_options; drupal_add_js($editor['library path'] . '/' . $file, $options); } else { @@ -335,17 +383,6 @@ function wysiwyg_load_editor($profile) { foreach ($files as $file) { drupal_add_css($editor['css path'] . '/' . $file); } - - drupal_add_js(array('wysiwyg' => array( - 'configs' => array($editor['name'] => array('global' => array( - // @todo Move into (global) editor settings. - // If JS compression is enabled, at least TinyMCE is unable to determine - // its own base path and exec mode since it can't find the script name. - 'editorBasePath' => base_path() . $editor['library path'], - 'execMode' => $library, - ))), - )), 'setting'); - $loaded[$name] = TRUE; } else { @@ -362,7 +399,6 @@ function wysiwyg_load_editor($profile) { 'enable' => t('Enable rich-text'), )), 'setting'); - $path = drupal_get_path('module', 'wysiwyg'); // Initialize our namespaces in the *header* to do not force editor // integration scripts to check and define Drupal.wysiwyg on its own. drupal_add_js($path . '/wysiwyg.init.js', array('group' => JS_LIBRARY)); @@ -619,7 +655,10 @@ function wysiwyg_get_css() { $files = array(); foreach (drupal_add_css() as $filepath => $info) { if ($info['group'] >= CSS_THEME && $info['media'] != 'print') { - if (file_exists($filepath)) { + if ($info['type'] == 'external') { + $files[] = $filepath; + } + elseif (file_exists($filepath)) { $files[] = base_path() . $filepath; } } @@ -809,6 +848,7 @@ function wysiwyg_get_all_editors() { 'libraries' => array(), 'version callback' => NULL, 'themes callback' => NULL, + 'settings form callback' => NULL, 'settings callback' => NULL, 'plugin callback' => NULL, 'plugin settings callback' => NULL, @@ -1077,3 +1117,19 @@ function _wysiwyg_process_include($module, $identifier, $path, $hook) { /** * @} End of "defgroup wysiwyg_api". */ + +/** + * Implements hook_features_api(). + */ +function wysiwyg_features_api() { + return array( + 'wysiwyg' => array( + 'name' => t('Wysiwyg profiles'), + 'default_hook' => 'wysiwyg_default_profiles', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'feature_source' => TRUE, + 'file' => drupal_get_path('module', 'wysiwyg') . '/wysiwyg.features.inc', + ), + ); +} +