Преглед на файлове

update 2.2 + precedent custom commits

Signed-off-by: bachy <git@g-u-i.net>
bachy преди 11 години
родител
ревизия
7884444ec6

+ 19 - 0
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

+ 333 - 268
LICENSE.txt

@@ -1,274 +1,339 @@
-GNU GENERAL PUBLIC LICENSE
-
-              Version 2, June 1991
-
-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
-
-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.
-
-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.
-
-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.
-
-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.
-
-           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".
-
-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.
-
-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:
-
-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.
-
-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.
-
-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.
+                    GNU GENERAL PUBLIC LICENSE
+                       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.
+
+                            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.
+
+  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.
+
+  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.
+
+  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.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    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".
+
+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.
+
+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:
+
+    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.
+
+    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.
+
+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.
-
-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,
-
-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.)
+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:
+
+    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,
+
+    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.
-
-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.
-
-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.
-
-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
+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.
+
+  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.
+
+  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
 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.
-
-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.
-
-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.
-
-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.
-
-               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.
-
-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
+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.
+
+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.
+
+  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.
+
+  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
+
+  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.
+
+                     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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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.

+ 2 - 1
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

+ 136 - 5
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 '<p class="warning">' . t('Do NOT download the "CKEditor for Drupal" edition.') . '</p>';
+}
+
 /**
  * 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.<br />Enter one class on each line in the format: !format. Example: !example<br />If left blank, CSS classes are automatically imported from loaded stylesheet(s).', array(
+      '!format' => '<code>[label]=[element].[class]</code>',
+      '!example' => '<code>Title=h1.title</code>',
+    ));
+    $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 <<<EOL
+window.CKEDITOR_BASEPATH = '$library_path';
+EOL;
+}
+
 /**
  * Return runtime editor settings for a given wysiwyg profile.
  *
@@ -127,8 +200,7 @@ function wysiwyg_ckeditor_themes($editor, $profile) {
  */
 function wysiwyg_ckeditor_settings($editor, $config, $theme) {
   $settings = array(
-    'baseHref' => $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']);
   }

+ 102 - 0
editors/epiceditor.inc

@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Editor integration functions for EpicEditor.
+ */
+
+/**
+ * Plugin implementation of hook_editor().
+ */
+function wysiwyg_epiceditor_editor() {
+  $editor['epiceditor'] = array(
+    'title' => '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;
+}
+

+ 2 - 2
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))));
     }
   }
 

+ 34 - 3
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();
   }
 };
 

+ 38 - 0
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('<div id="' + containerId + '" />');
+
+  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);

+ 16 - 1
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);
   }
 };
 

+ 13 - 0
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.
  */

+ 21 - 3
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);

+ 18 - 1
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);

+ 26 - 6
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();
   }
 };
 

+ 23 - 3
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();
   }
 };
 

+ 80 - 7
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, '<br />');
+    }
+    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);

+ 1 - 11
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');

+ 43 - 22
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 = $('<div class="wysiwygToolbar"></div>');
@@ -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;
   }
 };
 

+ 42 - 20
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&colon;'));
+  },
+
+  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);

+ 43 - 21
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&colon;'));
+  },
+
+  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);

+ 50 - 22
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&colon;'));
+  },
+
+  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);

+ 28 - 10
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;
   }
 };
 

+ 114 - 4
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,19 +74,71 @@ 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));
   }
 };
 

+ 3 - 3
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))));
     }
   }
 

+ 2 - 2
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))));
     }
   }
 

+ 83 - 41
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 <<<EOL
+window.tinyMCEPreInit = $settings;
+EOL;
+}
+
 /**
  * Return runtime editor settings for a given wysiwyg profile.
  *
@@ -181,6 +216,7 @@ function wysiwyg_tinymce_settings($editor, $config, $theme) {
     $settings['remove_linebreaks'] = $config['remove_linebreaks'];
   }
   if (isset($config['verify_html'])) {
+    // TinyMCE performs a type-agnostic comparison on this particular setting.
     $settings['verify_html'] = (bool) $config['verify_html'];
   }
 
@@ -192,8 +228,8 @@ function wysiwyg_tinymce_settings($editor, $config, $theme) {
     if ($config['css_setting'] == 'theme') {
       $settings['content_css'] = implode(',', wysiwyg_get_css());
     }
-    else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
-      $settings['content_css'] = strtr($config['css_path'], array('%b' => 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;
 }
 

+ 3 - 3
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))));
     }
   }
 

+ 22 - 21
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'),
   );
 }

+ 47 - 5
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().
  */

+ 3 - 3
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"
 

+ 43 - 0
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'];
+}

+ 18 - 74
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.
  */
 ?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
 
-<head>
-  <title><?php print $head_title; ?></title>
-  <?php print $head; ?>
-  <?php print $styles; ?>
-  <?php print $scripts; ?>
-  <script type="text/javascript"><?php /* Needed to avoid Flash of Unstyled Content in IE */ ?> </script>
-</head>
-<body>
   <div id="page">
-    <div id="container" class="clear-block">
-      <div id="main" class="column">
-        <?php if (!empty($breadcrumb)): ?><div id="breadcrumb"><?php print $breadcrumb; ?></div><?php endif; ?>
 
-        <div id="content">
-          <?php if (!empty($title)): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
-          <?php if (!empty($tabs)): ?><div class="tabs"><?php print $tabs; ?></div><?php endif; ?>
-          <?php if (!empty($messages)): print $messages; endif; ?>
-          <?php if (!empty($help)): print $help; endif; ?>
-          <div id="content-content" class="clear-block">
-            <?php print $content; ?>
-          </div>
-        </div>
+    <?php print $messages; ?>
 
-      </div>
-    </div>
-  </div>
-<?php print $closure; ?>
-</body>
-</html>
+    <div id="main-wrapper"><div id="main" class="clearfix">
+
+      <div id="content" class="column"><div class="section">
+        <a id="main-content"></a>
+        <?php print render($title_prefix); ?>
+        <?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
+        <?php print render($title_suffix); ?>
+        <?php if ($tabs): ?><div class="tabs"><?php print render($tabs); ?></div><?php endif; ?>
+        <?php print render($page['help']); ?>
+        <?php if ($action_links): ?><ul class="action-links"><?php print render($action_links); ?></ul><?php endif; ?>
+        <?php print render($page['content']); ?>
+      </div></div> <!-- /.section, /#content -->
+
+    </div></div> <!-- /#main, /#main-wrapper -->
+
+  </div> <!-- /#page -->

+ 56 - 19
wysiwyg.admin.inc

@@ -135,21 +135,22 @@ function wysiwyg_profile_form($form, &$form_state, $profile) {
           }
           $icon = file_exists($img_src) ? '<img src="' . base_path() . $img_src . '" title="' . $button . '" style="border: 1px solid grey; vertical-align: middle;" />' : '';
         }
-        $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 = '<p>' . t('Extract the archive and copy its contents into a new folder in the following location:<br /><code>@editor-path</code>', $targs) . '</p>';
       $instructions .= '<p>' . t('So the actual library can be found at:<br /><code>@library-filepath</code>', $targs) . '</p>';
 
+      // Add any install notes.
+      if (!empty($editor['install note callback']) && function_exists($editor['install note callback'])) {
+        $instructions .= '<div class="editor-install-note">' . $editor['install note callback']() . '</div>';
+      }
+
       $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';
 }
-

+ 77 - 0
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.
  *

+ 144 - 24
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);
 }
 

+ 3 - 3
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"
 

+ 1 - 0
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'),

+ 38 - 6
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);

+ 72 - 16
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 = '<p>' . 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.') . '</p>';
+      $output = '<p>' . 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.') . '</p>';
       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',
+    ),
+  );
+}
+