mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
34474: ALF-13169 Tomcat fails to shutdown - fix non daemon Timer's 34475: Part 1: Fix for ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - multi-threaded transaction and node tracking (off by default at the moment) - fix index/repo sync check failure if ACLs have been indexed but no transactions - minimise data sent back from query responses (not all stored fields) - added SOLR side config for HTTPClient pooling, cache sizing and tracker configuration - fixed SOLR incremental cache update for merges that end with all deletions in the old index - fixed unclosed stream in SolrKeyResourceLoader 34478: ALF-13050 - CIFS: Disabling account is not respected Also contains major rework of logging and exception handling. 34499: Fix for ALF-13150 34526: Fix for ALF-13288 34530: Minor CSS tweaks after changes for ALF-11991 34539: ALF-13176 - Implement Word for Mac 2011 Cifs Shuffle. 34541: ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - multi-threaded ACL tracking - multi-threaded statistics and reporting control - nodes that can not be indexed have an error record added to the index and do not block indexing the transaction (nodes unindexed due to exceptions can be found by ID query and the exception stored in the index) - nodes that are not-indexed have a minimal record added to the index for index consistency checking (unindexed nodes can be found by ID query) 34544: Add support for CIFS Level II shared oplocks. ALF-13138, ALF-13110. Fixed CIFS open for attributes only access preventing oplock on the following file open. Fixed reporting serialized copies of file access tokens as leaked. 34576: ALF-12767 - CIFS TextEdit - File has been modified outside TextEdit 34577: incorrectly checked in copy of network-protocol-context.xml 34580: ALF-13215: Ensure that permissions for everyone cannot be upgraded on moderated or private site. Fixed inconsistency between permissions shown in properties and in dialog 34582: ALF-13332: Updated modifier link for correct profile 34609: ALF-12740: Update to previous fix (only apply to IE8 and below) 34623: ALF-12767 - CIFS TextEdit - File has been modified outside TextEdit 34636: Fix for ALF-13365 SOLR: Recently modified docs dashlet sorts incorrectly - respect short property names on sort requests @cm:created and not require the full @{uri...}created 34659: ALF-2550 - added enterprise repo config files. 34715: Fix for __ShowDetails desktop action returned URL is truncated if hostname too long. ALF-13202. 34726: ALF-13293: Webdav: Version history lost after editing content in Finder 34738: ALF-7883: WebDAV: support HEAD method for folder - Fix by Pavel 34743: Fix for ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - batch fetch for nodes in transaction, acls in sets, and acls and readers - config for batch fetching - Better reporting for ACL set indexing 34747: ALF-13262: adding missing indexes for new schema's (activiti-schema create) + schema patch for existing schema 34817: Merged V4.0 to V4.0-BUG-FIX 34493: SPANISH: translation updates based on EN r34103 34498: Fixed ALF-12031: WCM: Content cannot be expired: avmExpiredContentTrigger is missing - Side-effect of ALF-11644: AVM cleanup jobs run when WCM is not installed - WORKAROUND: Get file 'root\projects\installer\wcm-bootstrap-context.xml' and use that 34525: Fix for ALF-13210: - removed "unsupported" from bulk filesystem import web pages 34531: Fix for ALF-13117 and ALF-13273 34549: Merged BRANCHES/DEV/BELARUS/HEAD-2012_03_15 to BRANCHES/V4.0: 34528: ALF-12874: 34552: ALF-13322: Fixed doc lib reload loop caused by "#" in folder name 34553: ALF-13311: Ensure images can be linked in TinyMCE create HTML content editor 34556: Minor: removed unused code 34557: Merged DEV to V4.0 34537: ALF-13035: Add "START WITH" parameter to IDENTITY field. ALF-13034: Add "optional" parameter for statement that drops index that was generated automatically. 34567: ALF-11047: Ensure that Explorer linked files and folders (from outside of sites) display correctly 34578: Fixes: ALF-11744: Dates rendered with the form service date control are rendered on the server, so show server time. - I've added the timezone to the display format and the ISO8601 date to the as an attribute on the HTML element to allow client side parsing - Adds client side parsing on the Doc Details page, so times are shown in the timezone of the user's browser. 34583: GERMAN: Translation update, based on EN r34103, Fixes: ALF-13075, 34584: FRENCH: Translation update based on EN r34103, Fixes: ALF-13002, ALF-13003, ALF-13020 34585: ITALIAN: Translation update based on EN r34103 34586: JAPANESE: Translation update based on EN r34103 34587: DUTCH: Translation update based on EN r34103, Fixes: ALF-12575. 34626: Fixes: ALF-13375 - Date rendering bug in search results 34630: Further fix for ALF-13375 that modifies Alfresco.util.formatDate's ISO8601 support for backward compatibility (e.g. passing in non ISO strings). 34635: ALF-12061: Mac support: Document Connection always throws an error - Case sensitivity fix by Pavel 34653: ALF-12308, ALF-12309, ALF-12554: Stack specific script errors 34655: Fix for ALF-12723 CMIS: Over-riding cm:autoVersionOnUpdateProps in custom model prevents startup 34656: Merged HEAD to BRANCHES/V4.0: 34654: Fixes: ALF-13389: Old element id used when setting event end date. 34657: Translation updates for all languages except JA. 34660: Fix to license driven config files to remove erroneous characters 34669: Merged DEV to V4.0 34663: ALF-12242: User activation issue InviteHelper.acceptNominatedInvitation() method was changed to enable user account in any case(no matter was it enabled/disabled before) 34681: Merged DEV/THEMIS2 to V4.0 34472: Document List Customization Refactor - SLingshotSiteModuleEvalutaor now has new <applyForNonSite> param that defaults to false for backward compability - Slingshot extension points, surf-doclist.get now uses 2 spring beans: * "resolver.doclib.doclistDataUrl" to get the repo doclist data url * "resolver.doclib.actionGroup" to get each item/nodes action group id 34692: Fix for ALF-12715 - Incorrect SPP working (mimetype not set on document stored via ADM Remote Store API) 34708: ALF-13239: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1) 34707: ALF-13239 Share rule to convert to PNG fails on JPG images - Issue was showing up in 4.0.1 as a change was made for iPad that introduced an imageOptions.isAutoOrient() setting. This forced a concatenation of null with " -auto-orient". However there are also crop and resize options that could also do this even in 3.4 Setting the commandOptions String to "" when null, is fine as this is how property value nulls are handled later anyway. 34718: JAPANESE: Localisation of Company specific contact information & addition of timezone to form control. 34719: FRENCH: File consistency tweak. 34746: ALF-12903: Create HTML content fix 34754: Merged PATCHES/V4.0.0 to V4.0 34750: Reinstate ${version.label} into version.number property 34810: Merged DEV to V4.0 (with corrections) 34807: ALF-13290 : Mac Support: Error appears after collaborator saves changes to the document deleteFailedThumbnailChildren method should be run as system user as it may fails with AccesssDenied if collaborator updates document 34876: Fix fo ALF-13503 Add SOLR client API tests to the SystemBuildTest project - SOLR API tests run embedded with SSL 34984: ALF-13109 - Correction to NTIOCtl.FsCtlCreateOrGetObjectId 35009: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.0-BUG-FIX: 35008: Fix for ALF-12817. Fixed as suggested - new method remove(). 35031: Fix for ALF-12309 35032: Fix fo ALF-13535 using CMIS, on-disk tickets cache can grow unbounded - expire tickets based on inactivity by default - added job to clean up expired tickets - all are configurable 35033: Fix fo ALF-13535 using CMIS, on-disk tickets cache can grow unbounded - avoid NPE for null tickets 35037: Fix for ALF-13505 SOLR tracking readers does not encode all uids correctly - fixed reader encoding 35049: ALF-13384 - Saving large Word (mac 2011) document via CIFS fails in Mac OS X Lion 35053: Merged V4.0 (V4.0.1) to V4.0-BUG-FIX (4.0.2) 34844: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1) 34843: ALF-5830 show_audit.ftl template doesn't work anymore - Removed L10n messages that are no longer used (should have been removed in 3.4.6 when this issue was fixed) 34847: Merged HEAD to BRANCHES/V4.0: 34804: Fixes: ALF-13309: Issue with over zealous HTML escaping with truncated descriptions in the Calendar Agenda view. 34861: ALF-13497: Merged PATCHES/V4.0.0 to V4.0 34813: ALF-13115: No feedback is given to the user when Approve/Reject is clicked for a task when they followed a link to the task in an email. - Fix by Pavel, reviewed by Kev - Now they get a confirmation message followed by a redirect to their dashboard 34862: Fix for ALF-10823 "allowGuestLogin=false" and Share then fills the alfresco error log with "Guest authentication not supported" Fix for ALF-12678 Errors in log on startup (ts.alfresco.com 4.0) - improved handling of 500 errors relating to GuestAuthNotSupported when alfresco.authentication.allowGuestLogin=false 34867: Merged DEV to V4.0 34565: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions WARN messages have been added if JBPM workflow definitions cannot be loaded in the model definitions. 34855: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions Reimplemented to handle all exceptions during constructing WorkflowInstances WorkflowTasks and WorkflowDefinitions. 34859: ALF-13074: JBPM workflow definitions are not resilient to missing model definitions Logger messages was changed to correspond the logger pattern. 34893: Translation updates for DE and ES. 34894: Fixes: ALF-13518; Updates Calendar event object's URL to work out of context. 34896: FRENCH: Translates new strings. 34915: Merged DEV to V4.0 34912: ALF-13267: There should not be a web-client-config-custom.xml in alfresco.war Move "modules\quickr\config\alfresco\extension\web-client-config-custom.xml" to "modules\quickr\config\alfresco\module\org.alfresco.module.quickr\ui\web-client-custom.xml". 34913: ALF-13267: There should not be a web-client-config-custom.xml in alfresco.war Delete "modules\quickr\config\alfresco\extension\web-client-config-custom.xml". 34916: ALF-13267: Merged V3.4 to V4.0 (and reversed previous duplicate fix) 24828: Merged BRANCHES/DEV/BELARUS/V3.4-2011_01_13 to BRANCHES/V3.4: 24824: ALF-6361: web-client-config-custom.xml doesn't work in /alfresco/tomcat/shared/classes/alfresco/extension 34929: ALF-12242: Issues activating users when more than one member in the authentication chain - Correction to fix that caused regressions ALF-13494, ALF-13498 - Need to check for the mutability of a user's authentication before trying to enable it - Also chaining of the authentication enabled attribute should assume true until false found, not the other way around 34930: ALF-12242: Reverted change to this class as it wasn't necessary and wouldn't work! 34932: ALF-13453: Enable XMLConstants.FEATURE_SECURE_PROCESSING feature on Transformer Factory to prevent remote code execution - Now SecureTransformerFactory should be used as a standard 34965: Merged PATCHES/V4.0.0 to V4.0 34959: ALF-13550: Fix for ALF-13546 SOLR tracking fails for nodes with content and no auditable aspect - NPE as there is no last modification date to use 34960: ALF-13551: Merged BRANCHES/DEV/V4.0-BUG-FIX to PATCHES\V4.0.0 - fix for ALF-13544 When SOLR encounters an error indexing a document, subsequent indexing does not occur 34541: ALF-13244 SOLR Multi-threaded tracking is required for performance - simultaneous document transformations - nodes that can not be indexed have an error record added to the index and do not block indexing the transaction (nodes unindexed due to exceptions can be found by ID query and the exception stored in the index) - nodes that are not-indexed have a minimal record added to the index for index consistency checking (unindexed nodes can be found by ID query) 34968: ALF-13453: Reversed XSLTProcessor and XSLTRenderingEngine changes for now as they break http://wiki.alfresco.com/wiki/WCM_Forms_Rendering and model handling via bsf extensions. A more sophisticated approach is required. See bug for more info. 34972: ALF-13340: Upgrade postgres JDBC driver to tested/supported version! 34997: ALF-13453, ALF-13565: Fully reverted revision 34932 as it prevents startup on Weblogic 34998: Merged V4.0-BUG-FIX to V4.0 34992: DUTCH: translation updates based on EN r34861 34993: FRENCH: Translation updates based on r34861 34994: ITALIAN: Translation updates based on r34861 35013: ALF-13561: Not found error after uploading new version - Fix by Pavel 35034: Fixes ALF-13570: Error loading event info panel. 35039: ALF-13573: Merged V3.4-BUG-FIX (3.4.9) to V4.0 (4.0.1) 35022: ALF-13451: Allow modules to configure mimetypes 35041: ALF-13466: Error is displayed by approve or reject wcm workflow - Fixed regression caused by ALF-4098 - Protected calls to new addNewChildrenIfAny() method with isDirectory() checks 35042: GERMAN: Translation updates based on r35029, and fixes ALF-12471. 35043: SPANISH: Translation updates based on r35029, and fixes ALF-12471. 35044: FRENCH: Translation updates based on r35029, and fixes ALF-12471. 35045: ITALIAN: Translation updates based on r35029, and fixes ALF-12471. 35046: JAPANESE: Translation updates based on r35029, and fixes ALF-12471. 35047: DUTCH: Translation updates based on r35029, and fixes ALF-12471. 35090: Remove Kofax. It has been migrated to integrations/kofax 35097: Added new file server cluster tests. Open for attributes only overlapped with open with oplock. Open with oplock with break to level II shared oplock. 35099: JLAN Client updates to support level II oplocks, required by new cluster tests. 35100: Various oplock related fixes, including problems opening file on second cluster node. ALF-13109. 35107: remove errant '>' 35116: ALF-13401 - Mac LION Powerpoint CIFS 35162: Removed spurious attempt to force a concurrency exception for getNodePair after a node had actually been deleted. Code would retry 50 times before failing. Reviewed with Derek, its not the node service's job to second guess that there may be a concurrency problem in a client's cache. 35164: Fix for ALF-13641 - Negative cases for date value in propertyNegative cases for date value in property. Today button 35169: ALF-13401, ALF-12393: Added exception translation to AbstractReindexComponent retrying transactions, following change in r35162 35172: ALF-13626: category.put.json.ftl has wrong bracket 35173: ALF-12749 - CIFS: Editing of ppt/pptx files fails (MacOSx specific) 35174: Fix for ALF-13556 - Sorting for custom model fields doesn't work for search results in Share 35176: Fix for ALF-4281 - Script error at 'Email space users' form 35186: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-34847 to BRANCHES/DEV/V4.0-BUG-FIX: 34875: Creating new branch from $FROM 34939: Merged BRANCHES/DEV/DAM/V4.0-BUG-FIX-34397 to BRANCHES/DEV/DAM/V4.0-BUG-FIX-34847: 34400: Creating new branch from $FROM 34422: Merged DEV/DAM-0.1 to DEV/DAM/V4.0-BUG-FIX-34397 34085: Allow for generateThumbnailUrl to accept a rendition name parameter. 34086: Changed simpleView view type switch to integer implementation rather than boolean. 34087: Pulled specific rendering code for simple and detail view into separate view renderer objects. 34092: If simpleView was stored as a boolean convert it to an integer for ALF-12952. 34423: Merged DEV/DAM/HEAD-34276 to DEV/DAM/V4.0-BUG-FIX-34397 34307: ALF-12952: Change DocumentList simpleView Nav Switch to an Int Implementation 34957: ALF-12952: Change DocumentList simpleView Nav Switch to an Int Implementation - Removed ability to specify index on registerViewRenderer - Added firing of setupAdditionalViewRenderers to make it easier for extensions to register themselves at the appropriate time 35021: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Changed viewRenderers to an object implementation with storage/retrieval via named properties or 'keys' 35050: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Renamed simpleView preference and option to viewRendererName - Reintroduced simpleView boolean preference and option as deprecated to allow deletion of old preference - Renamed viewRendererOrder to viewRendererNames - Added default viewRendererNames at DocumentList.options level - Renamed widgets.simpleDetailed to widgets.viewRendererSelect but did NOT change HTML id for backwards compatibility - Renamed onSimpleDetailed to onViewRendererSelect - Added deletion of deprecated simpleView preference if it exists 35056: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Made viewRenderer methods a proper Alfresco.ViewRenderer object which is more easily extended - Added name property to ViewRenderer constructor and changed registerViewRenderer to use that as a key - With more strictly defined ViewRenderers in place, changed select button to iterate over viewRendererNames rather than explicit list 35104: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Added markup tag around the document list container 35126: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Added markup tag documentListConstructorSetOptions around setOptions after DocumentList object constructor - Added markup tag documentListViewRendererSelect around view select buttons - Added markup tag documentListShowFolders around show folders button - Added markup tag documentListSortSelect around sort selection buttons - Renamed Alfresco.ViewRenderer to more specific Alfresco.DocumentListViewRenderer and private methods similarly - Added default for viewRendererName if it's undefined in options - Added check for availability of renderer specified in user preference, if not use default, and consolidated renderer index lookup 35179: ALF-12955: Share Document Library and Repository Browser Should Easily Allow for Additional Views - Removed documentListConstructorSetOptions 35194: Temp disable cifs text edit test. 35197: ALF-13097 - IMAP templates have wrong mimetype 35201: Merged V3.4-BUG-FIX to V4.0-BUG-FIX 34462: Merged DEV to V3.4-BUG-FIX 34461: ALF-10759: Advanced search fails for sub-element tags UITagSelector component which allows Advanced Search to add new tag option to search 34479: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 34477: ALF-13237: Yet another 13th hour Spring Surf Regression - Can't afford to pull in all the latest surf goodies so overriding PageImpl.class with one corresponding to Surf revision 1034 in WEB-INF/classes, just for 3.4.8 34515: ALF-9855: Alfresco side to support standard Adobe-Japan1 PDF fonts in swftools - Bitrock binaries provided 34518: ALF-13266: Ubuntu installation fails in non-obvious way when machine lacks sufficient memory - Fix from Bitrock - L10N required 34536: Merged DEV to V3.4-BUG-FIX 34529: ALF-13135: Impossible to Add new member on Workspace using email address NPE fix if AD users don't have e-mail address as a property. 34538: ALF-12812 Saving files with apps on Mac OS X Lion in CIFS doesn't invoke rules (Update rule fires BEFORE, FileFolderInterceptor recalcs HIDDEN and TEMPORARY ) 34542: Add support for Level II shared oplock. ALF-13093, ALF-12328. Fixed CIFS open for attributes only access preventing oplock on the following file open. 34543: Oplock and open for attributes fixes to the repo/AVM filesystems. ALF-13093, ALF-12328. 34579: ALF-13284: Removing obselete files 34603: ALF-10833 Alfresco does not show correct thumbnails for some specific kind of PDFs - Patched PDFRenderer-0.9.1 to return a null page if there was an error. The code structure did not lend itself to simply throwing the exception. - Modified PdfToImageContentTransformer to check for a null page and it then throws an AlfescoRuntimeException which causes the failover transformer to use the next transformer in the list: PDBBox which is able to transform the pdf and the image that was missing. 34617: Add missing source Java folder. 34629: ALF-13188: Content IO Channel not closed 34697: ALF-13149: Start up performance suffers if the alf_transaction table grows too large. 34712: ALF-13063: sample settings for DB2 34803: New installer translations from Gloria 34809: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_01_26 to V3.4-BUG-FIX (V3.4.9) << In addition to the 2 merged revisions, includes the change for ALF-11972 and test all-widgets.xsd >> 33715: ALF-11956: WCM accessibility - sandbox name oriented titles were added almost to all action links at 'Browse Website' page view; - adding titles to image tags functionality was added to ActionLinkRenderer, UIMenu and UISandboxes (this includes arrow icons for 'Web Forms' and 'Modified Items'); - titles were added to XForm Date/Time picker controls (text input and arrow buttons); - 'Click to edit' functionality via keyboard availability was added to XForms TinyMCE editor control (using 'Tab' key, 'Alt' + 'E' in IE or 'Alt' + 'Shift' + 'E' in FireFox); - additional i18n properties for Date/Time picker and action link titles were added 34625: ALF-11956: WCM accessibility Increasing XForms widgets readability by screen reader tools: - Tiny MCE 3.2.7 buttons; - required fields; - inputs labels; - VGroup, HGroup and Repeating widgets folding icons/buttons and others ALF-11972: Title attributes for the WCM form element xs:anyURI not included to allow multiple xs:anyURI file picker "Select" buttons to be distinguished by screen readers - Change defined in JIRA 34846: Translation updates: - FR: Missing Strings - DE: Fixes encoding issue 34881: ALF-13512: Merged PATCHES/V3.4.8 to V3.4-BUG-FIX 34829: ALF-12621: Sort order of folders including hyphens ( - ) are different in folder-tree and view on folders (in Share) - Switched from using JS sort to Java locale-based sort 34845: ALF-12621: Fixed array typing problems in previous checkin 34918: Fix for ALF-13385 Access DENIED api does not seem to work - changed default behaviour to any-deny-denies - config to switch back - needs custom port to 4.0 for SOLR - unit tests added 34919: Fix for ALF-13385 Access DENIED api does not seem to work - added property based configuration and default configuration check 34937: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_01_26 to V3.4-BUG-FIX (V3.4.9) 34886: ALF-11956: WCM accessibility - headings functionality is added. WAI-ARIA markup was used; - alert for XForms validation errors is added. WAI-ARIA markup was used; - previous accessibility changes tested and fixed against the new functionality 35003: Merged HEAD to V3.4-BUG-FIX 34673: Changed from time-based module and component names to GUID-based names. Not likely to affect anything. 35057: Fix for ALF-12590 Share - Document library doesn't return subfolders when parent space contains the character "- " - updated to the latest version of jaxen (which now includes saxpath) - the problem path is now parsed correctly 35074: ALF-13597: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX 34978: ALF-13489: Index tracker now has ability to distinguish create/update/rename/link/unlink - Will prevent unnecessary cascading PATH regeneration on remote cluster nodes - QNames and noderefs of parents in index compared with those in the database - Experimental - needs testing 34983: ALF-13489: Correction to renamed node detection 34985: ALF-13489: Even more foolproof parent assoc cross-referencing - Should handle duplicate QNames, etc. - Renames now just an add and a remove 35075: ALF-13598: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX 34872: Merged DEV (by Pavel) to PATCHES/V3.4.6 (and refactored) 34554: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. From now documents are locked for maximum 24 hours when working through WebDAV/Vti. 2. Session listeners were added for web-client and vti-module to allow handling session expiration event. 3. WebDAVLockService class was implemented. It is used by session listeners to perform session cleaning (forcibly unlock all documents that were persistently locked during http session). 4. LOCK/UNLOCK webdav methods and Get/Checkout/UncheckoutDocumentMethod vti methods where updated to correctly populate session list of locked documents. 34832: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. From now documents are locked for maximum 24 hours when working through WebDAV/Vti. 2. Session listener was added for webdav/vti to allow handling session expiration event. 3. LOCK/UNLOCK webdav methods and Get/Checkout/UncheckoutDocumentMethod vti methods where updated to use shared code to lock/unlock nodes. 34833: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. Remove unnecessary classes after 34554 rev. 34852: ALF-11777 : Persistent lock is left on document in certain use cases when editing online (spp) 1. Some changes after David's review of revisions 34832, 34833. 34874: ALF-11777: Fixed typo 35078: ALF-12785: BaseDownloadContentServlet could co into an infinite loop if asked to seek past the end of a file 35079: ALF-12490 "HTTP Status 500 - 00200935 Exception in Transaction" message error with webform - ALF-9524 fix assumed there were only switch elements in a form 35086: ALF-13563: Upgrade to Bitrock 8.1.0 to fix password validation issue 35095: ALF-12764: New distributable alfresco-enterprise-ear-3.4.9.zip - Like war zip, but contains .ear file instead of .wars and also contains WAS shared library - Means samples and other bits are finally available to non-Tomcat users 35103: Merged DEV to V3.4-BUG-FIX 35098: ALF-12776: if a user requests to join a moderated site, and that request is rejected, the rejection email is sent to the user-id and not the email id. Implemented Correct WorkflowModelModeratedInvitation.WF_PROP_REVIEW_COMMENTS field in configuration for moderatedInvitationReviewTask Person's email into emailAction PARAM_TO 35114: ALF-12766 Creating Web Content several users - different sandboxes - To be consistent with ALF-11440 PM comment 18-Dec-2011 and ALF-8787 A Manager should only be able to create a file in a sandbox if it is NOT locked somewhere else. - Not much can be done about the error message as the locked path is useful in other situations and it is not possible to issue a different message on create only 35121: ALF-11956: Merged BELARUS/V3.4-BUG-FIX-2012_04_05 to V3.4-BUG-FIX (V3.4.9) 35109: ALF-11956: WCM accessibility - Date/Time Pickers are made accessible via the keyboard and readable by JAWS (13, demo version). WAI-ARIA standard is used; - corrected 'expanded' state determination for Date/Time Pickers; - Modified Items and Web Forms arrow buttons are made accessible via the keyboard on the Browse Website page; - some changes per the description of the issue and per the comment of the 23-Feb-12 11:33 AM 35145: ALF-11990: CIFS login with case insensitive username is rejected - User name normalization moved to before MD4 hash retrieval 35151: Port of oplock related changes from v4.x. 35177: Fix for ALF-11936 - RSS feed from the activities dashlet produces invalid XML 35178: ALF-12631: removeChild requires delete permissions on the child node, even when it is a secondary association - now it doesn't (thanks to Andy's solution) - new ACL_PRI_CHILD_ASSOC_ON_CHILD ACL entry only enforces the permission on the child node when it is a primary association 35181: Merged DEV to V3.4-BUG-FIX 35165: ALF-13409: Invite to a site throws an error if an instance of invitation-moderated-workflow is started by a user whose account is subsequently deleted InvitationServiceImpl listens for person node deletions (it already implements beforeDeleteNode) and cancels invitations within beforeDeleteNode 35182: ALF-12567 Unable to create thumbnails for certain PDF files - The supplied PDF contains an invalid offset in the xref table. This turns out to be a quite common error resulting in thousands of Google hits. The offset is set to the string value "4294967295". This number in hex is FFFFFFFF. The value of an 4 byte int in C or Java with this value is -1. Neither PDFRenderer nor PDFBox have workarounds for this although lots of other systems do, which is why it is possible to view or edit it in other systems. Patched both PDFRenderer and PDFBox to handle this common error. 35185: ALF-13033: Friendlier error message when you try to delete non existent content from a sandbox 35191: ALF-13409: Fix build. 35192: Merged V3.4 to V3.4-BUG-FIX 35161: ALF-13624: Merged V4.0-BUG-FIX to V3.4 34474: ALF-13169 Tomcat fails to shut down - fix non daemon Timers (and punctuation!) 35163: ALF-13656: Merged HEAD to V3.4 31375: Fix for ALF-435 - Unfriendly error occurs when trying to delete renamed category from category page 35189: Italian translations from Gloria 35193: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 35125: Merged V3.4-BUG-FIX to V3.4 35156: Correction to merge in revision 35125 (a reintegrate merge rather than a selective merge) 35202: Merged V3.4-BUG-FIX to V4.0-BUG-FIX (RECORD ONLY) 34532: ALF-13233: Merged HEAD to V3.4-BUG-FIX 32960: ALF-11008 - Support the WebDAV DELETE method in SPP/VTI, with the special response required by SPP for locked documents 34559: ALF-13106: Merged HEAD to V3.4-BUG-FIX 28223: Merged DEV/SWIFT to HEAD (Tika and Poi) 30589: Upate Tika and add Ogg Vorbis support + tests 30673: Upgrade POI and Tika for recent fixes 31009: Bump the Tika version for some recent fixes 31010: Update the test audio files to include more metadata 31011: ALF-6170 Add missing audio model (needed in devcon demo) 31013: Update the MP3 extractor to output audio keys (related to ALF-6170), and refactor the audio extractors to share more common code. Also expands the audio extractor tests to share common code, and test more metadata. (Needed for devcon demo) 31022: Tika update for custom mimetypes enhancement 31023: Add @since tags where known, and do a quick coding standards sweep 31274: ALF-10813 follow-on - make it clearer that we're just creating the one detector, and switch to the new style version 31289: ALF-10803 - Upgrade Tika to add the extra WordPerfect mimetype 31553: ALF-10525 ACP mimetype detection fix, unit tests for it, and a NPE fix 31554: Update Tika to get the fix for TIKA-764 32105: ALF-11574 Upgrade Tika for the fix to TIKA-784, and add the DITA types to the Alfresco mimetype map 32138: Bump the Tika version for the updated TIKA-784 fix, and add an Alfresco side unit test for this case 32153: Update the vorbis jar to one that includes the license info more clearly in META-INF (without needing to read the POM) 32320: ALF-11650 Upgrade Tika for TIKA-789 (MPP Detection), and add tests that show it is now being correctly handled 32363: Update POI and Tika for the new code required to solve ALF-10980 (MPP Open/Change detection) 34560: ALF-13106: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33330: ALF-12487 In Mimetype Detection, if Tika detects a generic type of text/plain or XML, defer to the Alfresco filename based type (as we already do for octet stream) 33379: Add the TIFF mimetype 33380: Improve the stream to Tika conversion code, following review for THOR-952 33385: Upgrade to the latest Tika and POI, for recent bug fixes 33779: Upgrade Tika for ALF-12714 33782: ALF-12714 Add 3GPP/3GPP2 video, and MP4 Audio mimetypes 33783: Update Tika for more MP4/QuickTime support, and enable MP4 audio metadata extraction + "quick" testing 34561: ALF-13106: Fixed merge errors 34562: ALF-13106: Merged SWIFT to V3.4-BUG-FIX 26546: Have one copy of the Tika Config in spring, rather than several places fetching their own copy of the default one (either explicitly or implicitly). 34563: ALF-13106: Merged HEAD to V3.4-BUG-FIX 32264: Adding "quick" test resources for MS project. 34564: ALF-13106: Fix unit test 34752: GERMAN: Translation updates, based on EN: 34612 34753: SPANISH: Translation updates, based on EN: 34612 34755: FRENCH: Translation updates, based on EN: 34612 34756: ITALIAN: Translation updates, based on EN: 34612 34967: ALF-13552: Merged V4.0 to V3.4-BUG-FIX 34932: ALF-13453: Enable XMLConstants.FEATURE_SECURE_PROCESSING feature on Transformer Factory to prevent remote code execution - Now SecureTransformerFactory should be used as a standard 34971: ALF-13552: Merged V4.0 to V3.4-BUG-FIX 34968: ALF-13453: Reversed XSLTProcessor and XSLTRenderingEngine changes for now as they break http://wiki.alfresco.com/wiki/WCM_Forms_Rendering and model handling via bsf extensions. A more sophisticated approach is required. See bug for more info. 34982: ALF-13554: Merged V4.0 to V3.4-BUG-FIX 34972: ALF-13340: Upgrade postgres JDBC driver to tested/supported version! 34999: ALF-13552: Merged V4.0 to V3.4-BUG-FIX 34997: ALF-13453, ALF-13565: Fully reverted revision 34932 as it prevents startup on Weblogic 35000: Translation updates for DE, ES, IT. Based on EN r34846. 35015: ALF-13451: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33864: ALF-10736: JSF - Adding mimetype does not work on 3.4.x 35020: ALF-13451: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33863: ConfigSource for XMLConfigService which uses a ResourceFinder for wildcard-compatible lookups (UrlConfigSource does not support them) 35029: JAPANESE: Translation updates based on EN r34846 35212: ALF-13409: Deleting a person can now cancel their invitations. Cancelling invitations can delete inactive persons! So prevent infinite looping with a transaction local resource - Also fix up other invite related unit tests 35217: Merged DEV to V4.0-BUG-FIX 35214: ALF-12745 : AD-LDAP: alfresco hangs when upload user csv file Disable 'Upload User CSV File' button in Share admin console in case of AD-LDAP 35221: Avoid a NPE if Repository.getPerson() is called when no RunAsUser is active, instead return Null as for users with no defined NodeRef git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@35229 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
13241 lines
320 KiB
JavaScript
13241 lines
320 KiB
JavaScript
var tinymce = {
|
|
majorVersion : '3',
|
|
minorVersion : '2.7',
|
|
releaseDate : '2009-09-22',
|
|
|
|
_init : function() {
|
|
var t = this, d = document, w = window, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
|
|
|
|
t.isOpera = w.opera && opera.buildNumber;
|
|
|
|
t.isWebKit = /WebKit/.test(ua);
|
|
|
|
t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
|
|
|
|
t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
|
|
|
|
t.isGecko = !t.isWebKit && /Gecko/.test(ua);
|
|
|
|
t.isMac = ua.indexOf('Mac') != -1;
|
|
|
|
t.isAir = /adobeair/i.test(ua);
|
|
|
|
// TinyMCE .NET webcontrol might be setting the values for TinyMCE
|
|
if (w.tinyMCEPreInit) {
|
|
t.suffix = tinyMCEPreInit.suffix;
|
|
t.baseURL = tinyMCEPreInit.base;
|
|
t.query = tinyMCEPreInit.query;
|
|
return;
|
|
}
|
|
|
|
// Get suffix and base
|
|
t.suffix = '';
|
|
|
|
// If base element found, add that infront of baseURL
|
|
nl = d.getElementsByTagName('base');
|
|
for (i=0; i<nl.length; i++) {
|
|
if (v = nl[i].href) {
|
|
// Host only value like http://site.com or http://site.com:8008
|
|
if (/^https?:\/\/[^\/]+$/.test(v))
|
|
v += '/';
|
|
|
|
base = v ? v.match(/.*\//)[0] : ''; // Get only directory
|
|
}
|
|
}
|
|
|
|
function getBase(n) {
|
|
if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
|
|
if (/_(src|dev)\.js/g.test(n.src))
|
|
t.suffix = '_src';
|
|
|
|
if ((p = n.src.indexOf('?')) != -1)
|
|
t.query = n.src.substring(p + 1);
|
|
|
|
t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
|
|
|
|
// If path to script is relative and a base href was found add that one infront
|
|
// the src property will always be an absolute one on non IE browsers and IE 8
|
|
// so this logic will basically only be executed on older IE versions
|
|
if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
|
|
t.baseURL = base + t.baseURL;
|
|
|
|
return t.baseURL;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
// Check document
|
|
nl = d.getElementsByTagName('script');
|
|
for (i=0; i<nl.length; i++) {
|
|
if (getBase(nl[i]))
|
|
return;
|
|
}
|
|
|
|
// Check head
|
|
n = d.getElementsByTagName('head')[0];
|
|
if (n) {
|
|
nl = n.getElementsByTagName('script');
|
|
for (i=0; i<nl.length; i++) {
|
|
if (getBase(nl[i]))
|
|
return;
|
|
}
|
|
}
|
|
|
|
return;
|
|
},
|
|
|
|
is : function(o, t) {
|
|
var n = typeof(o);
|
|
|
|
if (!t)
|
|
return n != 'undefined';
|
|
|
|
if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
|
|
return true;
|
|
|
|
return n == t;
|
|
},
|
|
|
|
each : function(o, cb, s) {
|
|
var n, l;
|
|
|
|
if (!o)
|
|
return 0;
|
|
|
|
s = s || o;
|
|
|
|
if (typeof(o.length) != 'undefined') {
|
|
// Indexed arrays, needed for Safari
|
|
for (n=0, l = o.length; n<l; n++) {
|
|
if (cb.call(s, o[n], n, o) === false)
|
|
return 0;
|
|
}
|
|
} else {
|
|
// Hashtables
|
|
for (n in o) {
|
|
if (o.hasOwnProperty(n)) {
|
|
if (cb.call(s, o[n], n, o) === false)
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
},
|
|
|
|
|
|
map : function(a, f) {
|
|
var o = [];
|
|
|
|
tinymce.each(a, function(v) {
|
|
o.push(f(v));
|
|
});
|
|
|
|
return o;
|
|
},
|
|
|
|
grep : function(a, f) {
|
|
var o = [];
|
|
|
|
tinymce.each(a, function(v) {
|
|
if (!f || f(v))
|
|
o.push(v);
|
|
});
|
|
|
|
return o;
|
|
},
|
|
|
|
inArray : function(a, v) {
|
|
var i, l;
|
|
|
|
if (a) {
|
|
for (i = 0, l = a.length; i < l; i++) {
|
|
if (a[i] === v)
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
},
|
|
|
|
extend : function(o, e) {
|
|
var i, a = arguments;
|
|
|
|
for (i=1; i<a.length; i++) {
|
|
e = a[i];
|
|
|
|
tinymce.each(e, function(v, n) {
|
|
if (typeof(v) !== 'undefined')
|
|
o[n] = v;
|
|
});
|
|
}
|
|
|
|
return o;
|
|
},
|
|
|
|
|
|
trim : function(s) {
|
|
return (s ? '' + s : '').replace(/^\s*|\s*$/g, '');
|
|
},
|
|
|
|
create : function(s, p) {
|
|
var t = this, sp, ns, cn, scn, c, de = 0;
|
|
|
|
// Parse : <prefix> <class>:<super class>
|
|
s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
|
|
cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
|
|
|
|
// Create namespace for new class
|
|
ns = t.createNS(s[3].replace(/\.\w+$/, ''));
|
|
|
|
// Class already exists
|
|
if (ns[cn])
|
|
return;
|
|
|
|
// Make pure static class
|
|
if (s[2] == 'static') {
|
|
ns[cn] = p;
|
|
|
|
if (this.onCreate)
|
|
this.onCreate(s[2], s[3], ns[cn]);
|
|
|
|
return;
|
|
}
|
|
|
|
// Create default constructor
|
|
if (!p[cn]) {
|
|
p[cn] = function() {};
|
|
de = 1;
|
|
}
|
|
|
|
// Add constructor and methods
|
|
ns[cn] = p[cn];
|
|
t.extend(ns[cn].prototype, p);
|
|
|
|
// Extend
|
|
if (s[5]) {
|
|
sp = t.resolve(s[5]).prototype;
|
|
scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
|
|
|
|
// Extend constructor
|
|
c = ns[cn];
|
|
if (de) {
|
|
// Add passthrough constructor
|
|
ns[cn] = function() {
|
|
return sp[scn].apply(this, arguments);
|
|
};
|
|
} else {
|
|
// Add inherit constructor
|
|
ns[cn] = function() {
|
|
this.parent = sp[scn];
|
|
return c.apply(this, arguments);
|
|
};
|
|
}
|
|
ns[cn].prototype[cn] = ns[cn];
|
|
|
|
// Add super methods
|
|
t.each(sp, function(f, n) {
|
|
ns[cn].prototype[n] = sp[n];
|
|
});
|
|
|
|
// Add overridden methods
|
|
t.each(p, function(f, n) {
|
|
// Extend methods if needed
|
|
if (sp[n]) {
|
|
ns[cn].prototype[n] = function() {
|
|
this.parent = sp[n];
|
|
return f.apply(this, arguments);
|
|
};
|
|
} else {
|
|
if (n != cn)
|
|
ns[cn].prototype[n] = f;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add static methods
|
|
t.each(p['static'], function(f, n) {
|
|
ns[cn][n] = f;
|
|
});
|
|
|
|
if (this.onCreate)
|
|
this.onCreate(s[2], s[3], ns[cn].prototype);
|
|
},
|
|
|
|
walk : function(o, f, n, s) {
|
|
s = s || this;
|
|
|
|
if (o) {
|
|
if (n)
|
|
o = o[n];
|
|
|
|
tinymce.each(o, function(o, i) {
|
|
if (f.call(s, o, i, n) === false)
|
|
return false;
|
|
|
|
tinymce.walk(o, f, n, s);
|
|
});
|
|
}
|
|
},
|
|
|
|
createNS : function(n, o) {
|
|
var i, v;
|
|
|
|
o = o || window;
|
|
|
|
n = n.split('.');
|
|
for (i=0; i<n.length; i++) {
|
|
v = n[i];
|
|
|
|
if (!o[v])
|
|
o[v] = {};
|
|
|
|
o = o[v];
|
|
}
|
|
|
|
return o;
|
|
},
|
|
|
|
resolve : function(n, o) {
|
|
var i, l;
|
|
|
|
o = o || window;
|
|
|
|
n = n.split('.');
|
|
for (i = 0, l = n.length; i < l; i++) {
|
|
o = o[n[i]];
|
|
|
|
if (!o)
|
|
break;
|
|
}
|
|
|
|
return o;
|
|
},
|
|
|
|
addUnload : function(f, s) {
|
|
var t = this, w = window;
|
|
|
|
f = {func : f, scope : s || this};
|
|
|
|
if (!t.unloads) {
|
|
function unload() {
|
|
var li = t.unloads, o, n;
|
|
|
|
if (li) {
|
|
// Call unload handlers
|
|
for (n in li) {
|
|
o = li[n];
|
|
|
|
if (o && o.func)
|
|
o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
|
|
}
|
|
|
|
// Detach unload function
|
|
if (w.detachEvent) {
|
|
w.detachEvent('onbeforeunload', fakeUnload);
|
|
w.detachEvent('onunload', unload);
|
|
} else if (w.removeEventListener)
|
|
w.removeEventListener('unload', unload, false);
|
|
|
|
// Destroy references
|
|
t.unloads = o = li = w = unload = 0;
|
|
|
|
// Run garbarge collector on IE
|
|
if (window.CollectGarbage)
|
|
window.CollectGarbage();
|
|
}
|
|
};
|
|
|
|
function fakeUnload() {
|
|
var d = document;
|
|
|
|
// Is there things still loading, then do some magic
|
|
if (d.readyState == 'interactive') {
|
|
function stop() {
|
|
// Prevent memory leak
|
|
d.detachEvent('onstop', stop);
|
|
|
|
// Call unload handler
|
|
if (unload)
|
|
unload();
|
|
|
|
d = 0;
|
|
};
|
|
|
|
// Fire unload when the currently loading page is stopped
|
|
if (d)
|
|
d.attachEvent('onstop', stop);
|
|
|
|
// Remove onstop listener after a while to prevent the unload function
|
|
// to execute if the user presses cancel in an onbeforeunload
|
|
// confirm dialog and then presses the browser stop button
|
|
window.setTimeout(function() {
|
|
if (d)
|
|
d.detachEvent('onstop', stop);
|
|
}, 0);
|
|
}
|
|
};
|
|
|
|
// Attach unload handler
|
|
if (w.attachEvent) {
|
|
w.attachEvent('onunload', unload);
|
|
w.attachEvent('onbeforeunload', fakeUnload);
|
|
} else if (w.addEventListener)
|
|
w.addEventListener('unload', unload, false);
|
|
|
|
// Setup initial unload handler array
|
|
t.unloads = [f];
|
|
} else
|
|
t.unloads.push(f);
|
|
|
|
return f;
|
|
},
|
|
|
|
removeUnload : function(f) {
|
|
var u = this.unloads, r = null;
|
|
|
|
tinymce.each(u, function(o, i) {
|
|
if (o && o.func == f) {
|
|
u.splice(i, 1);
|
|
r = f;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return r;
|
|
},
|
|
|
|
explode : function(s, d) {
|
|
return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
|
|
},
|
|
|
|
_addVer : function(u) {
|
|
var v;
|
|
|
|
if (!this.query)
|
|
return u;
|
|
|
|
v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
|
|
|
|
if (u.indexOf('#') == -1)
|
|
return u + v;
|
|
|
|
return u.replace('#', v + '#');
|
|
}
|
|
|
|
};
|
|
|
|
// Required for GZip AJAX loading
|
|
window.tinymce = tinymce;
|
|
|
|
// Initialize the API
|
|
tinymce._init();
|
|
tinymce.create('tinymce.util.Dispatcher', {
|
|
scope : null,
|
|
listeners : null,
|
|
|
|
Dispatcher : function(s) {
|
|
this.scope = s || this;
|
|
this.listeners = [];
|
|
},
|
|
|
|
add : function(cb, s) {
|
|
this.listeners.push({cb : cb, scope : s || this.scope});
|
|
|
|
return cb;
|
|
},
|
|
|
|
addToTop : function(cb, s) {
|
|
this.listeners.unshift({cb : cb, scope : s || this.scope});
|
|
|
|
return cb;
|
|
},
|
|
|
|
remove : function(cb) {
|
|
var l = this.listeners, o = null;
|
|
|
|
tinymce.each(l, function(c, i) {
|
|
if (cb == c.cb) {
|
|
o = cb;
|
|
l.splice(i, 1);
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return o;
|
|
},
|
|
|
|
dispatch : function() {
|
|
var s, a = arguments, i, li = this.listeners, c;
|
|
|
|
// Needs to be a real loop since the listener count might change while looping
|
|
// And this is also more efficient
|
|
for (i = 0; i<li.length; i++) {
|
|
c = li[i];
|
|
s = c.cb.apply(c.scope, a);
|
|
|
|
if (s === false)
|
|
break;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
});
|
|
(function() {
|
|
var each = tinymce.each;
|
|
|
|
tinymce.create('tinymce.util.URI', {
|
|
URI : function(u, s) {
|
|
var t = this, o, a, b;
|
|
|
|
// Trim whitespace
|
|
u = tinymce.trim(u);
|
|
|
|
// Default settings
|
|
s = t.settings = s || {};
|
|
|
|
// Strange app protocol or local anchor
|
|
if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
|
|
t.source = u;
|
|
return;
|
|
}
|
|
|
|
// Absolute path with no host, fake host and protocol
|
|
if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
|
|
u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
|
|
|
|
// Relative path http:// or protocol relative //path
|
|
if (!/^\w*:?\/\//.test(u))
|
|
u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
|
|
|
|
// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
|
|
u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
|
|
u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
|
|
each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
|
|
var s = u[i];
|
|
|
|
// Zope 3 workaround, they use @@something
|
|
if (s)
|
|
s = s.replace(/\(mce_at\)/g, '@@');
|
|
|
|
t[v] = s;
|
|
});
|
|
|
|
if (b = s.base_uri) {
|
|
if (!t.protocol)
|
|
t.protocol = b.protocol;
|
|
|
|
if (!t.userInfo)
|
|
t.userInfo = b.userInfo;
|
|
|
|
if (!t.port && t.host == 'mce_host')
|
|
t.port = b.port;
|
|
|
|
if (!t.host || t.host == 'mce_host')
|
|
t.host = b.host;
|
|
|
|
t.source = '';
|
|
}
|
|
|
|
//t.path = t.path || '/';
|
|
},
|
|
|
|
setPath : function(p) {
|
|
var t = this;
|
|
|
|
p = /^(.*?)\/?(\w+)?$/.exec(p);
|
|
|
|
// Update path parts
|
|
t.path = p[0];
|
|
t.directory = p[1];
|
|
t.file = p[2];
|
|
|
|
// Rebuild source
|
|
t.source = '';
|
|
t.getURI();
|
|
},
|
|
|
|
toRelative : function(u) {
|
|
var t = this, o;
|
|
|
|
if (u === "./")
|
|
return u;
|
|
|
|
u = new tinymce.util.URI(u, {base_uri : t});
|
|
|
|
// Not on same domain/port or protocol
|
|
if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
|
|
return u.getURI();
|
|
|
|
o = t.toRelPath(t.path, u.path);
|
|
|
|
// Add query
|
|
if (u.query)
|
|
o += '?' + u.query;
|
|
|
|
// Add anchor
|
|
if (u.anchor)
|
|
o += '#' + u.anchor;
|
|
|
|
return o;
|
|
},
|
|
|
|
toAbsolute : function(u, nh) {
|
|
var u = new tinymce.util.URI(u, {base_uri : this});
|
|
|
|
return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
|
|
},
|
|
|
|
toRelPath : function(base, path) {
|
|
var items, bp = 0, out = '', i, l;
|
|
|
|
// Split the paths
|
|
base = base.substring(0, base.lastIndexOf('/'));
|
|
base = base.split('/');
|
|
items = path.split('/');
|
|
|
|
if (base.length >= items.length) {
|
|
for (i = 0, l = base.length; i < l; i++) {
|
|
if (i >= items.length || base[i] != items[i]) {
|
|
bp = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (base.length < items.length) {
|
|
for (i = 0, l = items.length; i < l; i++) {
|
|
if (i >= base.length || base[i] != items[i]) {
|
|
bp = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bp == 1)
|
|
return path;
|
|
|
|
for (i = 0, l = base.length - (bp - 1); i < l; i++)
|
|
out += "../";
|
|
|
|
for (i = bp - 1, l = items.length; i < l; i++) {
|
|
if (i != bp - 1)
|
|
out += "/" + items[i];
|
|
else
|
|
out += items[i];
|
|
}
|
|
|
|
return out;
|
|
},
|
|
|
|
toAbsPath : function(base, path) {
|
|
var i, nb = 0, o = [], tr, outPath;
|
|
|
|
// Split paths
|
|
tr = /\/$/.test(path) ? '/' : '';
|
|
base = base.split('/');
|
|
path = path.split('/');
|
|
|
|
// Remove empty chunks
|
|
each(base, function(k) {
|
|
if (k)
|
|
o.push(k);
|
|
});
|
|
|
|
base = o;
|
|
|
|
// Merge relURLParts chunks
|
|
for (i = path.length - 1, o = []; i >= 0; i--) {
|
|
// Ignore empty or .
|
|
if (path[i].length == 0 || path[i] == ".")
|
|
continue;
|
|
|
|
// Is parent
|
|
if (path[i] == '..') {
|
|
nb++;
|
|
continue;
|
|
}
|
|
|
|
// Move up
|
|
if (nb > 0) {
|
|
nb--;
|
|
continue;
|
|
}
|
|
|
|
o.push(path[i]);
|
|
}
|
|
|
|
i = base.length - nb;
|
|
|
|
// If /a/b/c or /
|
|
if (i <= 0)
|
|
outPath = o.reverse().join('/');
|
|
else
|
|
outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
|
|
|
|
// Add front / if it's needed
|
|
if (outPath.indexOf('/') !== 0)
|
|
outPath = '/' + outPath;
|
|
|
|
// Add traling / if it's needed
|
|
if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
|
|
outPath += tr;
|
|
|
|
return outPath;
|
|
},
|
|
|
|
getURI : function(nh) {
|
|
var s, t = this;
|
|
|
|
// Rebuild source
|
|
if (!t.source || nh) {
|
|
s = '';
|
|
|
|
if (!nh) {
|
|
if (t.protocol)
|
|
s += t.protocol + '://';
|
|
|
|
if (t.userInfo)
|
|
s += t.userInfo + '@';
|
|
|
|
if (t.host)
|
|
s += t.host;
|
|
|
|
if (t.port)
|
|
s += ':' + t.port;
|
|
}
|
|
|
|
if (t.path)
|
|
s += t.path;
|
|
|
|
if (t.query)
|
|
s += '?' + t.query;
|
|
|
|
if (t.anchor)
|
|
s += '#' + t.anchor;
|
|
|
|
t.source = s;
|
|
}
|
|
|
|
return t.source;
|
|
}
|
|
});
|
|
})();
|
|
(function() {
|
|
var each = tinymce.each;
|
|
|
|
tinymce.create('static tinymce.util.Cookie', {
|
|
getHash : function(n) {
|
|
var v = this.get(n), h;
|
|
|
|
if (v) {
|
|
each(v.split('&'), function(v) {
|
|
v = v.split('=');
|
|
h = h || {};
|
|
h[unescape(v[0])] = unescape(v[1]);
|
|
});
|
|
}
|
|
|
|
return h;
|
|
},
|
|
|
|
setHash : function(n, v, e, p, d, s) {
|
|
var o = '';
|
|
|
|
each(v, function(v, k) {
|
|
o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
|
|
});
|
|
|
|
this.set(n, o, e, p, d, s);
|
|
},
|
|
|
|
get : function(n) {
|
|
var c = document.cookie, e, p = n + "=", b;
|
|
|
|
// Strict mode
|
|
if (!c)
|
|
return;
|
|
|
|
b = c.indexOf("; " + p);
|
|
|
|
if (b == -1) {
|
|
b = c.indexOf(p);
|
|
|
|
if (b != 0)
|
|
return null;
|
|
} else
|
|
b += 2;
|
|
|
|
e = c.indexOf(";", b);
|
|
|
|
if (e == -1)
|
|
e = c.length;
|
|
|
|
return unescape(c.substring(b + p.length, e));
|
|
},
|
|
|
|
set : function(n, v, e, p, d, s) {
|
|
document.cookie = n + "=" + escape(v) +
|
|
((e) ? "; expires=" + e.toGMTString() : "") +
|
|
((p) ? "; path=" + escape(p) : "") +
|
|
((d) ? "; domain=" + d : "") +
|
|
((s) ? "; secure" : "");
|
|
},
|
|
|
|
remove : function(n, p) {
|
|
var d = new Date();
|
|
|
|
d.setTime(d.getTime() - 1000);
|
|
|
|
this.set(n, '', d, p, d);
|
|
}
|
|
});
|
|
})();
|
|
tinymce.create('static tinymce.util.JSON', {
|
|
serialize : function(o) {
|
|
var i, v, s = tinymce.util.JSON.serialize, t;
|
|
|
|
if (o == null)
|
|
return 'null';
|
|
|
|
t = typeof o;
|
|
|
|
if (t == 'string') {
|
|
v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
|
|
|
|
return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
|
|
i = v.indexOf(b);
|
|
|
|
if (i + 1)
|
|
return '\\' + v.charAt(i + 1);
|
|
|
|
a = b.charCodeAt().toString(16);
|
|
|
|
return '\\u' + '0000'.substring(a.length) + a;
|
|
}) + '"';
|
|
}
|
|
|
|
if (t == 'object') {
|
|
if (o.hasOwnProperty && o instanceof Array) {
|
|
for (i=0, v = '['; i<o.length; i++)
|
|
v += (i > 0 ? ',' : '') + s(o[i]);
|
|
|
|
return v + ']';
|
|
}
|
|
|
|
v = '{';
|
|
|
|
for (i in o)
|
|
v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
|
|
|
|
return v + '}';
|
|
}
|
|
|
|
return '' + o;
|
|
},
|
|
|
|
parse : function(s) {
|
|
try {
|
|
return eval('(' + s + ')');
|
|
} catch (ex) {
|
|
// Ignore
|
|
}
|
|
}
|
|
|
|
});
|
|
tinymce.create('static tinymce.util.XHR', {
|
|
send : function(o) {
|
|
var x, t, w = window, c = 0;
|
|
|
|
// Default settings
|
|
o.scope = o.scope || this;
|
|
o.success_scope = o.success_scope || o.scope;
|
|
o.error_scope = o.error_scope || o.scope;
|
|
o.async = o.async === false ? false : true;
|
|
o.data = o.data || '';
|
|
|
|
function get(s) {
|
|
x = 0;
|
|
|
|
try {
|
|
x = new ActiveXObject(s);
|
|
} catch (ex) {
|
|
}
|
|
|
|
return x;
|
|
};
|
|
|
|
x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
|
|
|
|
if (x) {
|
|
if (x.overrideMimeType)
|
|
x.overrideMimeType(o.content_type);
|
|
|
|
x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
|
|
|
|
if (o.content_type)
|
|
x.setRequestHeader('Content-Type', o.content_type);
|
|
|
|
x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
|
|
x.send(o.data);
|
|
|
|
function ready() {
|
|
if (!o.async || x.readyState == 4 || c++ > 10000) {
|
|
if (o.success && c < 10000 && x.status == 200)
|
|
o.success.call(o.success_scope, '' + x.responseText, x, o);
|
|
else if (o.error)
|
|
o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
|
|
|
|
x = null;
|
|
} else
|
|
w.setTimeout(ready, 10);
|
|
};
|
|
|
|
// Syncronous request
|
|
if (!o.async)
|
|
return ready();
|
|
|
|
// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
|
|
t = w.setTimeout(ready, 10);
|
|
}
|
|
}
|
|
});
|
|
(function() {
|
|
var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
|
|
|
|
tinymce.create('tinymce.util.JSONRequest', {
|
|
JSONRequest : function(s) {
|
|
this.settings = extend({
|
|
}, s);
|
|
this.count = 0;
|
|
},
|
|
|
|
send : function(o) {
|
|
var ecb = o.error, scb = o.success;
|
|
|
|
o = extend(this.settings, o);
|
|
|
|
o.success = function(c, x) {
|
|
c = JSON.parse(c);
|
|
|
|
if (typeof(c) == 'undefined') {
|
|
c = {
|
|
error : 'JSON Parse error.'
|
|
};
|
|
}
|
|
|
|
if (c.error)
|
|
ecb.call(o.error_scope || o.scope, c.error, x);
|
|
else
|
|
scb.call(o.success_scope || o.scope, c.result);
|
|
};
|
|
|
|
o.error = function(ty, x) {
|
|
ecb.call(o.error_scope || o.scope, ty, x);
|
|
};
|
|
|
|
o.data = JSON.serialize({
|
|
id : o.id || 'c' + (this.count++),
|
|
method : o.method,
|
|
params : o.params
|
|
});
|
|
|
|
// JSON content type for Ruby on rails. Bug: #1883287
|
|
o.content_type = 'application/json';
|
|
|
|
XHR.send(o);
|
|
},
|
|
|
|
'static' : {
|
|
sendRPC : function(o) {
|
|
return new tinymce.util.JSONRequest().send(o);
|
|
}
|
|
}
|
|
});
|
|
}());(function(tinymce) {
|
|
// Shorten names
|
|
var each = tinymce.each, is = tinymce.is;
|
|
var isWebKit = tinymce.isWebKit, isIE = tinymce.isIE;
|
|
|
|
tinymce.create('tinymce.dom.DOMUtils', {
|
|
doc : null,
|
|
root : null,
|
|
files : null,
|
|
pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
|
|
props : {
|
|
"for" : "htmlFor",
|
|
"class" : "className",
|
|
className : "className",
|
|
checked : "checked",
|
|
disabled : "disabled",
|
|
maxlength : "maxLength",
|
|
readonly : "readOnly",
|
|
selected : "selected",
|
|
value : "value",
|
|
id : "id",
|
|
name : "name",
|
|
type : "type"
|
|
},
|
|
|
|
DOMUtils : function(d, s) {
|
|
var t = this;
|
|
|
|
t.doc = d;
|
|
t.win = window;
|
|
t.files = {};
|
|
t.cssFlicker = false;
|
|
t.counter = 0;
|
|
t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
|
|
t.stdMode = d.documentMode === 8;
|
|
|
|
t.settings = s = tinymce.extend({
|
|
keep_values : false,
|
|
hex_colors : 1,
|
|
process_html : 1
|
|
}, s);
|
|
|
|
// Fix IE6SP2 flicker and check it failed for pre SP2
|
|
if (tinymce.isIE6) {
|
|
try {
|
|
d.execCommand('BackgroundImageCache', false, true);
|
|
} catch (e) {
|
|
t.cssFlicker = true;
|
|
}
|
|
}
|
|
|
|
tinymce.addUnload(t.destroy, t);
|
|
},
|
|
|
|
getRoot : function() {
|
|
var t = this, s = t.settings;
|
|
|
|
return (s && t.get(s.root_element)) || t.doc.body;
|
|
},
|
|
|
|
getViewPort : function(w) {
|
|
var d, b;
|
|
|
|
w = !w ? this.win : w;
|
|
d = w.document;
|
|
b = this.boxModel ? d.documentElement : d.body;
|
|
|
|
// Returns viewport size excluding scrollbars
|
|
return {
|
|
x : w.pageXOffset || b.scrollLeft,
|
|
y : w.pageYOffset || b.scrollTop,
|
|
w : w.innerWidth || b.clientWidth,
|
|
h : w.innerHeight || b.clientHeight
|
|
};
|
|
},
|
|
|
|
getRect : function(e) {
|
|
var p, t = this, sr;
|
|
|
|
e = t.get(e);
|
|
p = t.getPos(e);
|
|
sr = t.getSize(e);
|
|
|
|
return {
|
|
x : p.x,
|
|
y : p.y,
|
|
w : sr.w,
|
|
h : sr.h
|
|
};
|
|
},
|
|
|
|
getSize : function(e) {
|
|
var t = this, w, h;
|
|
|
|
e = t.get(e);
|
|
w = t.getStyle(e, 'width');
|
|
h = t.getStyle(e, 'height');
|
|
|
|
// Non pixel value, then force offset/clientWidth
|
|
if (w.indexOf('px') === -1)
|
|
w = 0;
|
|
|
|
// Non pixel value, then force offset/clientWidth
|
|
if (h.indexOf('px') === -1)
|
|
h = 0;
|
|
|
|
return {
|
|
w : parseInt(w) || e.offsetWidth || e.clientWidth,
|
|
h : parseInt(h) || e.offsetHeight || e.clientHeight
|
|
};
|
|
},
|
|
|
|
getParent : function(n, f, r) {
|
|
return this.getParents(n, f, r, false);
|
|
},
|
|
|
|
getParents : function(n, f, r, c) {
|
|
var t = this, na, se = t.settings, o = [];
|
|
|
|
n = t.get(n);
|
|
c = c === undefined;
|
|
|
|
if (se.strict_root)
|
|
r = r || t.getRoot();
|
|
|
|
// Wrap node name as func
|
|
if (is(f, 'string')) {
|
|
na = f;
|
|
|
|
if (f === '*') {
|
|
f = function(n) {return n.nodeType == 1;};
|
|
} else {
|
|
f = function(n) {
|
|
return t.is(n, na);
|
|
};
|
|
}
|
|
}
|
|
|
|
while (n) {
|
|
if (n == r || !n.nodeType || n.nodeType === 9)
|
|
break;
|
|
|
|
if (!f || f(n)) {
|
|
if (c)
|
|
o.push(n);
|
|
else
|
|
return n;
|
|
}
|
|
|
|
n = n.parentNode;
|
|
}
|
|
|
|
return c ? o : null;
|
|
},
|
|
|
|
get : function(e) {
|
|
var n;
|
|
|
|
if (e && this.doc && typeof(e) == 'string') {
|
|
n = e;
|
|
e = this.doc.getElementById(e);
|
|
|
|
// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
|
|
if (e && e.id !== n)
|
|
return this.doc.getElementsByName(n)[1];
|
|
}
|
|
|
|
return e;
|
|
},
|
|
|
|
getNext : function(node, selector) {
|
|
return this._findSib(node, selector, 'nextSibling');
|
|
},
|
|
|
|
getPrev : function(node, selector) {
|
|
return this._findSib(node, selector, 'previousSibling');
|
|
},
|
|
|
|
|
|
select : function(pa, s) {
|
|
var t = this;
|
|
|
|
return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
|
|
},
|
|
|
|
is : function(n, patt) {
|
|
return tinymce.dom.Sizzle.matches(patt, n.nodeType ? [n] : n).length > 0;
|
|
},
|
|
|
|
|
|
add : function(p, n, a, h, c) {
|
|
var t = this;
|
|
|
|
return this.run(p, function(p) {
|
|
var e, k;
|
|
|
|
e = is(n, 'string') ? t.doc.createElement(n) : n;
|
|
t.setAttribs(e, a);
|
|
|
|
if (h) {
|
|
if (h.nodeType)
|
|
e.appendChild(h);
|
|
else
|
|
t.setHTML(e, h);
|
|
}
|
|
|
|
return !c ? p.appendChild(e) : e;
|
|
});
|
|
},
|
|
|
|
create : function(n, a, h) {
|
|
return this.add(this.doc.createElement(n), n, a, h, 1);
|
|
},
|
|
|
|
createHTML : function(n, a, h) {
|
|
var o = '', t = this, k;
|
|
|
|
o += '<' + n;
|
|
|
|
for (k in a) {
|
|
if (a.hasOwnProperty(k))
|
|
o += ' ' + k + '="' + t.encode(a[k]) + '"';
|
|
}
|
|
|
|
if (tinymce.is(h))
|
|
return o + '>' + h + '</' + n + '>';
|
|
|
|
return o + ' />';
|
|
},
|
|
|
|
remove : function(n, k) {
|
|
var t = this;
|
|
|
|
return this.run(n, function(n) {
|
|
var p, g, i;
|
|
|
|
p = n.parentNode;
|
|
|
|
if (!p)
|
|
return null;
|
|
|
|
if (k) {
|
|
for (i = n.childNodes.length - 1; i >= 0; i--)
|
|
t.insertAfter(n.childNodes[i], n);
|
|
|
|
//each(n.childNodes, function(c) {
|
|
// p.insertBefore(c.cloneNode(true), n);
|
|
//});
|
|
}
|
|
|
|
// Fix IE psuedo leak
|
|
if (t.fixPsuedoLeaks) {
|
|
p = n.cloneNode(true);
|
|
k = 'IELeakGarbageBin';
|
|
g = t.get(k) || t.add(t.doc.body, 'div', {id : k, style : 'display:none'});
|
|
g.appendChild(n);
|
|
g.innerHTML = '';
|
|
|
|
return p;
|
|
}
|
|
|
|
return p.removeChild(n);
|
|
});
|
|
},
|
|
|
|
setStyle : function(n, na, v) {
|
|
var t = this;
|
|
|
|
return t.run(n, function(e) {
|
|
var s, i;
|
|
|
|
s = e.style;
|
|
|
|
// Camelcase it, if needed
|
|
na = na.replace(/-(\D)/g, function(a, b){
|
|
return b.toUpperCase();
|
|
});
|
|
|
|
// Default px suffix on these
|
|
if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
|
|
v += 'px';
|
|
|
|
switch (na) {
|
|
case 'opacity':
|
|
// IE specific opacity
|
|
if (isIE) {
|
|
s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
|
|
|
|
if (!n.currentStyle || !n.currentStyle.hasLayout)
|
|
s.display = 'inline-block';
|
|
}
|
|
|
|
// Fix for older browsers
|
|
s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
|
|
break;
|
|
|
|
case 'float':
|
|
isIE ? s.styleFloat = v : s.cssFloat = v;
|
|
break;
|
|
|
|
default:
|
|
s[na] = v || '';
|
|
}
|
|
|
|
// Force update of the style data
|
|
if (t.settings.update_styles)
|
|
t.setAttrib(e, 'mce_style');
|
|
});
|
|
},
|
|
|
|
getStyle : function(n, na, c) {
|
|
n = this.get(n);
|
|
|
|
if (!n)
|
|
return false;
|
|
|
|
// Gecko
|
|
if (this.doc.defaultView && c) {
|
|
// Remove camelcase
|
|
na = na.replace(/[A-Z]/g, function(a){
|
|
return '-' + a;
|
|
});
|
|
|
|
try {
|
|
return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
|
|
} catch (ex) {
|
|
// Old safari might fail
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Camelcase it, if needed
|
|
na = na.replace(/-(\D)/g, function(a, b){
|
|
return b.toUpperCase();
|
|
});
|
|
|
|
if (na == 'float')
|
|
na = isIE ? 'styleFloat' : 'cssFloat';
|
|
|
|
// IE & Opera
|
|
if (n.currentStyle && c)
|
|
return n.currentStyle[na];
|
|
|
|
return n.style[na];
|
|
},
|
|
|
|
setStyles : function(e, o) {
|
|
var t = this, s = t.settings, ol;
|
|
|
|
ol = s.update_styles;
|
|
s.update_styles = 0;
|
|
|
|
each(o, function(v, n) {
|
|
t.setStyle(e, n, v);
|
|
});
|
|
|
|
// Update style info
|
|
s.update_styles = ol;
|
|
if (s.update_styles)
|
|
t.setAttrib(e, s.cssText);
|
|
},
|
|
|
|
setAttrib : function(e, n, v) {
|
|
var t = this;
|
|
|
|
// Whats the point
|
|
if (!e || !n)
|
|
return;
|
|
|
|
// Strict XML mode
|
|
if (t.settings.strict)
|
|
n = n.toLowerCase();
|
|
|
|
return this.run(e, function(e) {
|
|
var s = t.settings;
|
|
|
|
switch (n) {
|
|
case "style":
|
|
if (!is(v, 'string')) {
|
|
each(v, function(v, n) {
|
|
t.setStyle(e, n, v);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// No mce_style for elements with these since they might get resized by the user
|
|
if (s.keep_values) {
|
|
if (v && !t._isRes(v))
|
|
e.setAttribute('mce_style', v, 2);
|
|
else
|
|
e.removeAttribute('mce_style', 2);
|
|
}
|
|
|
|
e.style.cssText = v;
|
|
break;
|
|
|
|
case "class":
|
|
e.className = v || ''; // Fix IE null bug
|
|
break;
|
|
|
|
case "src":
|
|
case "href":
|
|
if (s.keep_values) {
|
|
if (s.url_converter)
|
|
v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
|
|
|
|
t.setAttrib(e, 'mce_' + n, v, 2);
|
|
}
|
|
|
|
break;
|
|
|
|
case "shape":
|
|
e.setAttribute('mce_style', v);
|
|
break;
|
|
}
|
|
|
|
if (is(v) && v !== null && v.length !== 0)
|
|
e.setAttribute(n, '' + v, 2);
|
|
else
|
|
e.removeAttribute(n, 2);
|
|
});
|
|
},
|
|
|
|
setAttribs : function(e, o) {
|
|
var t = this;
|
|
|
|
return this.run(e, function(e) {
|
|
each(o, function(v, n) {
|
|
t.setAttrib(e, n, v);
|
|
});
|
|
});
|
|
},
|
|
|
|
getAttrib : function(e, n, dv) {
|
|
var v, t = this;
|
|
|
|
e = t.get(e);
|
|
|
|
if (!e || e.nodeType !== 1)
|
|
return false;
|
|
|
|
if (!is(dv))
|
|
dv = '';
|
|
|
|
// Try the mce variant for these
|
|
if (/^(src|href|style|coords|shape)$/.test(n)) {
|
|
v = e.getAttribute("mce_" + n);
|
|
|
|
if (v)
|
|
return v;
|
|
}
|
|
|
|
if (isIE && t.props[n]) {
|
|
v = e[t.props[n]];
|
|
v = v && v.nodeValue ? v.nodeValue : v;
|
|
}
|
|
|
|
if (!v)
|
|
v = e.getAttribute(n, 2);
|
|
|
|
// Check boolean attribs
|
|
if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
|
|
if (e[t.props[n]] === true && v === '')
|
|
return n;
|
|
|
|
return v ? n : '';
|
|
}
|
|
|
|
// Inner input elements will override attributes on form elements
|
|
if (e.nodeName === "FORM" && e.getAttributeNode(n))
|
|
return e.getAttributeNode(n).nodeValue;
|
|
|
|
if (n === 'style') {
|
|
v = v || e.style.cssText;
|
|
|
|
if (v) {
|
|
v = t.serializeStyle(t.parseStyle(v));
|
|
|
|
if (t.settings.keep_values && !t._isRes(v))
|
|
e.setAttribute('mce_style', v);
|
|
}
|
|
}
|
|
|
|
// Remove Apple and WebKit stuff
|
|
if (isWebKit && n === "class" && v)
|
|
v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
|
|
|
|
// Handle IE issues
|
|
if (isIE) {
|
|
switch (n) {
|
|
case 'rowspan':
|
|
case 'colspan':
|
|
// IE returns 1 as default value
|
|
if (v === 1)
|
|
v = '';
|
|
|
|
break;
|
|
|
|
case 'size':
|
|
// IE returns +0 as default value for size
|
|
if (v === '+0' || v === 20 || v === 0)
|
|
v = '';
|
|
|
|
break;
|
|
|
|
case 'width':
|
|
case 'height':
|
|
case 'vspace':
|
|
case 'checked':
|
|
case 'disabled':
|
|
case 'readonly':
|
|
if (v === 0)
|
|
v = '';
|
|
|
|
break;
|
|
|
|
case 'hspace':
|
|
// IE returns -1 as default value
|
|
if (v === -1)
|
|
v = '';
|
|
|
|
break;
|
|
|
|
case 'maxlength':
|
|
case 'tabindex':
|
|
// IE returns default value
|
|
if (v === 32768 || v === 2147483647 || v === '32768')
|
|
v = '';
|
|
|
|
break;
|
|
|
|
case 'multiple':
|
|
case 'compact':
|
|
case 'noshade':
|
|
case 'nowrap':
|
|
if (v === 65535)
|
|
return n;
|
|
|
|
return dv;
|
|
|
|
case 'shape':
|
|
v = v.toLowerCase();
|
|
break;
|
|
|
|
default:
|
|
// IE has odd anonymous function for event attributes
|
|
if (n.indexOf('on') === 0 && v)
|
|
v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
|
|
}
|
|
}
|
|
|
|
return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
|
|
},
|
|
|
|
getPos : function(n, ro) {
|
|
var t = this, x = 0, y = 0, e, d = t.doc, r;
|
|
|
|
n = t.get(n);
|
|
ro = ro || d.body;
|
|
|
|
if (n) {
|
|
// Use getBoundingClientRect on IE, Opera has it but it's not perfect
|
|
if (isIE && !t.stdMode) {
|
|
n = n.getBoundingClientRect();
|
|
e = t.boxModel ? d.documentElement : d.body;
|
|
x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
|
|
x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
|
|
n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset
|
|
|
|
return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
|
|
}
|
|
|
|
r = n;
|
|
while (r && r != ro && r.nodeType) {
|
|
x += r.offsetLeft || 0;
|
|
y += r.offsetTop || 0;
|
|
r = r.offsetParent;
|
|
}
|
|
|
|
r = n.parentNode;
|
|
while (r && r != ro && r.nodeType) {
|
|
x -= r.scrollLeft || 0;
|
|
y -= r.scrollTop || 0;
|
|
r = r.parentNode;
|
|
}
|
|
}
|
|
|
|
return {x : x, y : y};
|
|
},
|
|
|
|
parseStyle : function(st) {
|
|
var t = this, s = t.settings, o = {};
|
|
|
|
if (!st)
|
|
return o;
|
|
|
|
function compress(p, s, ot) {
|
|
var t, r, b, l;
|
|
|
|
// Get values and check it it needs compressing
|
|
t = o[p + '-top' + s];
|
|
if (!t)
|
|
return;
|
|
|
|
r = o[p + '-right' + s];
|
|
if (t != r)
|
|
return;
|
|
|
|
b = o[p + '-bottom' + s];
|
|
if (r != b)
|
|
return;
|
|
|
|
l = o[p + '-left' + s];
|
|
if (b != l)
|
|
return;
|
|
|
|
// Compress
|
|
o[ot] = l;
|
|
delete o[p + '-top' + s];
|
|
delete o[p + '-right' + s];
|
|
delete o[p + '-bottom' + s];
|
|
delete o[p + '-left' + s];
|
|
};
|
|
|
|
function compress2(ta, a, b, c) {
|
|
var t;
|
|
|
|
t = o[a];
|
|
if (!t)
|
|
return;
|
|
|
|
t = o[b];
|
|
if (!t)
|
|
return;
|
|
|
|
t = o[c];
|
|
if (!t)
|
|
return;
|
|
|
|
// Compress
|
|
o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
|
|
delete o[a];
|
|
delete o[b];
|
|
delete o[c];
|
|
};
|
|
|
|
st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
|
|
|
|
each(st.split(';'), function(v) {
|
|
var sv, ur = [];
|
|
|
|
if (v) {
|
|
v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
|
|
v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
|
|
v = v.split(':');
|
|
sv = tinymce.trim(v[1]);
|
|
sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
|
|
|
|
sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
|
|
return t.toHex(v);
|
|
});
|
|
|
|
if (s.url_converter) {
|
|
sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
|
|
return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
|
|
});
|
|
}
|
|
|
|
o[tinymce.trim(v[0]).toLowerCase()] = sv;
|
|
}
|
|
});
|
|
|
|
compress("border", "", "border");
|
|
compress("border", "-width", "border-width");
|
|
compress("border", "-color", "border-color");
|
|
compress("border", "-style", "border-style");
|
|
compress("padding", "", "padding");
|
|
compress("margin", "", "margin");
|
|
compress2('border', 'border-width', 'border-style', 'border-color');
|
|
|
|
if (isIE) {
|
|
// Remove pointless border
|
|
if (o.border == 'medium none')
|
|
o.border = '';
|
|
}
|
|
|
|
return o;
|
|
},
|
|
|
|
serializeStyle : function(o) {
|
|
var s = '';
|
|
|
|
each(o, function(v, k) {
|
|
if (k && v) {
|
|
if (tinymce.isGecko && k.indexOf('-moz-') === 0)
|
|
return;
|
|
|
|
switch (k) {
|
|
case 'color':
|
|
case 'background-color':
|
|
v = v.toLowerCase();
|
|
break;
|
|
}
|
|
|
|
s += (s ? ' ' : '') + k + ': ' + v + ';';
|
|
}
|
|
});
|
|
|
|
return s;
|
|
},
|
|
|
|
loadCSS : function(u) {
|
|
var t = this, d = t.doc, head;
|
|
|
|
if (!u)
|
|
u = '';
|
|
|
|
head = t.select('head')[0];
|
|
|
|
each(u.split(','), function(u) {
|
|
var link;
|
|
|
|
if (t.files[u])
|
|
return;
|
|
|
|
t.files[u] = true;
|
|
link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
|
|
|
|
// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
|
|
// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
|
|
// It's ugly but it seems to work fine.
|
|
if (isIE && d.documentMode) {
|
|
link.onload = function() {
|
|
d.recalc();
|
|
link.onload = null;
|
|
};
|
|
}
|
|
|
|
head.appendChild(link);
|
|
});
|
|
},
|
|
|
|
addClass : function(e, c) {
|
|
return this.run(e, function(e) {
|
|
var o;
|
|
|
|
if (!c)
|
|
return 0;
|
|
|
|
if (this.hasClass(e, c))
|
|
return e.className;
|
|
|
|
o = this.removeClass(e, c);
|
|
|
|
return e.className = (o != '' ? (o + ' ') : '') + c;
|
|
});
|
|
},
|
|
|
|
removeClass : function(e, c) {
|
|
var t = this, re;
|
|
|
|
return t.run(e, function(e) {
|
|
var v;
|
|
|
|
if (t.hasClass(e, c)) {
|
|
if (!re)
|
|
re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
|
|
|
|
v = e.className.replace(re, ' ');
|
|
|
|
return e.className = tinymce.trim(v != ' ' ? v : '');
|
|
}
|
|
|
|
return e.className;
|
|
});
|
|
},
|
|
|
|
hasClass : function(n, c) {
|
|
n = this.get(n);
|
|
|
|
if (!n || !c)
|
|
return false;
|
|
|
|
return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
|
|
},
|
|
|
|
show : function(e) {
|
|
return this.setStyle(e, 'display', 'block');
|
|
},
|
|
|
|
hide : function(e) {
|
|
return this.setStyle(e, 'display', 'none');
|
|
},
|
|
|
|
isHidden : function(e) {
|
|
e = this.get(e);
|
|
|
|
return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
|
|
},
|
|
|
|
uniqueId : function(p) {
|
|
return (!p ? 'mce_' : p) + (this.counter++);
|
|
},
|
|
|
|
setHTML : function(e, h) {
|
|
var t = this;
|
|
|
|
return this.run(e, function(e) {
|
|
var x, i, nl, n, p, x;
|
|
|
|
h = t.processHTML(h);
|
|
|
|
if (isIE) {
|
|
function set() {
|
|
try {
|
|
// IE will remove comments from the beginning
|
|
// unless you padd the contents with something
|
|
e.innerHTML = '<br />' + h;
|
|
e.removeChild(e.firstChild);
|
|
} catch (ex) {
|
|
// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
|
|
// This seems to fix this problem
|
|
|
|
// Remove all child nodes
|
|
while (e.firstChild)
|
|
e.firstChild.removeNode();
|
|
|
|
// Create new div with HTML contents and a BR infront to keep comments
|
|
x = t.create('div');
|
|
x.innerHTML = '<br />' + h;
|
|
|
|
// Add all children from div to target
|
|
each (x.childNodes, function(n, i) {
|
|
// Skip br element
|
|
if (i)
|
|
e.appendChild(n);
|
|
});
|
|
}
|
|
};
|
|
|
|
// IE has a serious bug when it comes to paragraphs it can produce an invalid
|
|
// DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
|
|
// It seems to be that IE doesn't like a root block element placed inside another root block element
|
|
if (t.settings.fix_ie_paragraphs)
|
|
h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 mce_keep="true"> </p>');
|
|
|
|
set();
|
|
|
|
if (t.settings.fix_ie_paragraphs) {
|
|
// Check for odd paragraphs this is a sign of a broken DOM
|
|
nl = e.getElementsByTagName("p");
|
|
for (i = nl.length - 1, x = 0; i >= 0; i--) {
|
|
n = nl[i];
|
|
|
|
if (!n.hasChildNodes()) {
|
|
if (!n.mce_keep) {
|
|
x = 1; // Is broken
|
|
break;
|
|
}
|
|
|
|
n.removeAttribute('mce_keep');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Time to fix the madness IE left us
|
|
if (x) {
|
|
// So if we replace the p elements with divs and mark them and then replace them back to paragraphs
|
|
// after we use innerHTML we can fix the DOM tree
|
|
h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 mce_tmp="1">');
|
|
h = h.replace(/<\/p>/g, '</div>');
|
|
|
|
// Set the new HTML with DIVs
|
|
set();
|
|
|
|
// Replace all DIV elements with he mce_tmp attibute back to paragraphs
|
|
// This is needed since IE has a annoying bug see above for details
|
|
// This is a slow process but it has to be done. :(
|
|
if (t.settings.fix_ie_paragraphs) {
|
|
nl = e.getElementsByTagName("DIV");
|
|
for (i = nl.length - 1; i >= 0; i--) {
|
|
n = nl[i];
|
|
|
|
// Is it a temp div
|
|
if (n.mce_tmp) {
|
|
// Create new paragraph
|
|
p = t.doc.createElement('p');
|
|
|
|
// Copy all attributes
|
|
n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
|
|
var v;
|
|
|
|
if (b !== 'mce_tmp') {
|
|
v = n.getAttribute(b);
|
|
|
|
if (!v && b === 'class')
|
|
v = n.className;
|
|
|
|
p.setAttribute(b, v);
|
|
}
|
|
});
|
|
|
|
// Append all children to new paragraph
|
|
for (x = 0; x<n.childNodes.length; x++)
|
|
p.appendChild(n.childNodes[x].cloneNode(true));
|
|
|
|
// Replace div with new paragraph
|
|
n.swapNode(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
e.innerHTML = h;
|
|
|
|
return h;
|
|
});
|
|
},
|
|
|
|
processHTML : function(h) {
|
|
var t = this, s = t.settings, codeBlocks = [];
|
|
|
|
if (!s.process_html)
|
|
return h;
|
|
|
|
// Convert strong and em to b and i in FF since it can't handle them
|
|
if (tinymce.isGecko) {
|
|
h = h.replace(/<(\/?)strong>|<strong( [^>]+)>/gi, '<$1b$2>');
|
|
h = h.replace(/<(\/?)em>|<em( [^>]+)>/gi, '<$1i$2>');
|
|
} else if (isIE) {
|
|
h = h.replace(/'/g, '''); // IE can't handle apos
|
|
h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
|
|
}
|
|
|
|
// Fix some issues
|
|
h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
|
|
|
|
// Store away src and href in mce_src and mce_href since browsers mess them up
|
|
if (s.keep_values) {
|
|
// Wrap scripts and styles in comments for serialization purposes
|
|
if (/<script|noscript|style/i.test(h)) {
|
|
function trim(s) {
|
|
// Remove prefix and suffix code for element
|
|
s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
|
|
s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
|
|
s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
|
|
s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
|
|
|
|
return s;
|
|
};
|
|
|
|
// Wrap the script contents in CDATA and keep them from executing
|
|
h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
|
|
// Force type attribute
|
|
if (!attribs)
|
|
attribs = ' type="text/javascript"';
|
|
|
|
// Convert the src attribute of the scripts
|
|
attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
|
|
if (s.url_converter)
|
|
url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
|
|
|
|
return 'mce_src="' + url + '"';
|
|
});
|
|
|
|
// Wrap text contents
|
|
if (tinymce.trim(text)) {
|
|
codeBlocks.push(trim(text));
|
|
text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
|
|
}
|
|
|
|
return '<mce:script' + attribs + '>' + text + '</mce:script>';
|
|
});
|
|
|
|
// Wrap style elements
|
|
h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
|
|
// Wrap text contents
|
|
if (text) {
|
|
codeBlocks.push(trim(text));
|
|
text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
|
|
}
|
|
|
|
return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' mce_bogus="1">' + text + '</style>';
|
|
});
|
|
|
|
// Wrap noscript elements
|
|
h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
|
|
return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '--') + '--></mce:noscript>';
|
|
});
|
|
}
|
|
|
|
h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
|
|
|
|
// Remove false bool attributes and force attributes into xhtml style attr="attr"
|
|
h = h.replace(/<([\w:]+) [^>]*(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)[^>]*>/gi, function(val) {
|
|
function handle(val, name, value) {
|
|
// Remove false/0 attribs
|
|
if (value === 'false' || value === '0')
|
|
return '';
|
|
|
|
return ' ' + name + '="' + name + '"';
|
|
};
|
|
|
|
val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)=[\"]([^\"]+)[\"]/gi, handle); // W3C
|
|
val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)=[\']([^\']+)[\']/gi, handle); // W3C
|
|
val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)=([^\s\"\'>]+)/gi, handle); // IE
|
|
val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)([\s>])/gi, ' $1="$1"$2'); // Force attr="attr"
|
|
|
|
return val;
|
|
});
|
|
|
|
// Process all tags with src, href or style
|
|
h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) {
|
|
function handle(m, b, c) {
|
|
var u = c;
|
|
|
|
// Tag already got a mce_ version
|
|
if (a.indexOf('mce_' + b) != -1)
|
|
return m;
|
|
|
|
if (b == 'style') {
|
|
// No mce_style for elements with these since they might get resized by the user
|
|
if (t._isRes(c))
|
|
return m;
|
|
|
|
// Parse and serialize the style to convert for example uppercase styles like "BORDER: 1px"
|
|
u = t.encode(t.serializeStyle(t.parseStyle(u)));
|
|
} else if (b != 'coords' && b != 'shape') {
|
|
if (s.url_converter)
|
|
u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n));
|
|
}
|
|
|
|
return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"';
|
|
};
|
|
|
|
a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C
|
|
a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C
|
|
|
|
return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE
|
|
});
|
|
|
|
// Restore script blocks
|
|
h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
|
|
return codeBlocks[idx];
|
|
});
|
|
}
|
|
|
|
return h;
|
|
},
|
|
|
|
getOuterHTML : function(e) {
|
|
var d;
|
|
|
|
e = this.get(e);
|
|
|
|
if (!e)
|
|
return null;
|
|
|
|
if (e.outerHTML !== undefined)
|
|
return e.outerHTML;
|
|
|
|
d = (e.ownerDocument || this.doc).createElement("body");
|
|
d.appendChild(e.cloneNode(true));
|
|
|
|
return d.innerHTML;
|
|
},
|
|
|
|
setOuterHTML : function(e, h, d) {
|
|
var t = this;
|
|
|
|
function setHTML(e, h, d) {
|
|
var n, tp;
|
|
|
|
tp = d.createElement("body");
|
|
tp.innerHTML = h;
|
|
|
|
n = tp.lastChild;
|
|
while (n) {
|
|
t.insertAfter(n.cloneNode(true), e);
|
|
n = n.previousSibling;
|
|
}
|
|
|
|
t.remove(e);
|
|
};
|
|
|
|
return this.run(e, function(e) {
|
|
e = t.get(e);
|
|
|
|
// Only set HTML on elements
|
|
if (e.nodeType == 1) {
|
|
d = d || e.ownerDocument || t.doc;
|
|
|
|
if (isIE) {
|
|
try {
|
|
// Try outerHTML for IE it sometimes produces an unknown runtime error
|
|
if (isIE && e.nodeType == 1)
|
|
e.outerHTML = h;
|
|
else
|
|
setHTML(e, h, d);
|
|
} catch (ex) {
|
|
// Fix for unknown runtime error
|
|
setHTML(e, h, d);
|
|
}
|
|
} else
|
|
setHTML(e, h, d);
|
|
}
|
|
});
|
|
},
|
|
|
|
decode : function(s) {
|
|
var e, n, v;
|
|
|
|
// Look for entities to decode
|
|
if (/&[^;]+;/.test(s)) {
|
|
// Decode the entities using a div element not super efficient but less code
|
|
e = this.doc.createElement("div");
|
|
e.innerHTML = s;
|
|
n = e.firstChild;
|
|
v = '';
|
|
|
|
if (n) {
|
|
do {
|
|
v += n.nodeValue;
|
|
} while (n.nextSibling);
|
|
}
|
|
|
|
return v || s;
|
|
}
|
|
|
|
return s;
|
|
},
|
|
|
|
encode : function(s) {
|
|
return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) {
|
|
switch (c) {
|
|
case '&':
|
|
return '&';
|
|
|
|
case '"':
|
|
return '"';
|
|
|
|
case '<':
|
|
return '<';
|
|
|
|
case '>':
|
|
return '>';
|
|
}
|
|
|
|
return c;
|
|
}) : s;
|
|
},
|
|
|
|
insertAfter : function(n, r) {
|
|
var t = this;
|
|
|
|
r = t.get(r);
|
|
|
|
return this.run(n, function(n) {
|
|
var p, ns;
|
|
|
|
p = r.parentNode;
|
|
ns = r.nextSibling;
|
|
|
|
if (ns)
|
|
p.insertBefore(n, ns);
|
|
else
|
|
p.appendChild(n);
|
|
|
|
return n;
|
|
});
|
|
},
|
|
|
|
isBlock : function(n) {
|
|
if (n.nodeType && n.nodeType !== 1)
|
|
return false;
|
|
|
|
n = n.nodeName || n;
|
|
|
|
return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TH|TBODY|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(n);
|
|
},
|
|
|
|
replace : function(n, o, k) {
|
|
var t = this;
|
|
|
|
if (is(o, 'array'))
|
|
n = n.cloneNode(true);
|
|
|
|
return t.run(o, function(o) {
|
|
if (k) {
|
|
each(o.childNodes, function(c) {
|
|
n.appendChild(c.cloneNode(true));
|
|
});
|
|
}
|
|
|
|
// Fix IE psuedo leak for elements since replacing elements if fairly common
|
|
// Will break parentNode for some unknown reason
|
|
if (t.fixPsuedoLeaks && o.nodeType === 1) {
|
|
o.parentNode.insertBefore(n, o);
|
|
t.remove(o);
|
|
return n;
|
|
}
|
|
|
|
return o.parentNode.replaceChild(n, o);
|
|
});
|
|
},
|
|
|
|
findCommonAncestor : function(a, b) {
|
|
var ps = a, pe;
|
|
|
|
while (ps) {
|
|
pe = b;
|
|
|
|
while (pe && ps != pe)
|
|
pe = pe.parentNode;
|
|
|
|
if (ps == pe)
|
|
break;
|
|
|
|
ps = ps.parentNode;
|
|
}
|
|
|
|
if (!ps && a.ownerDocument)
|
|
return a.ownerDocument.documentElement;
|
|
|
|
return ps;
|
|
},
|
|
|
|
toHex : function(s) {
|
|
var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
|
|
|
|
function hex(s) {
|
|
s = parseInt(s).toString(16);
|
|
|
|
return s.length > 1 ? s : '0' + s; // 0 -> 00
|
|
};
|
|
|
|
if (c) {
|
|
s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
|
|
|
|
return s;
|
|
}
|
|
|
|
return s;
|
|
},
|
|
|
|
getClasses : function() {
|
|
var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
|
|
|
|
if (t.classes)
|
|
return t.classes;
|
|
|
|
function addClasses(s) {
|
|
// IE style imports
|
|
each(s.imports, function(r) {
|
|
addClasses(r);
|
|
});
|
|
|
|
each(s.cssRules || s.rules, function(r) {
|
|
// Real type or fake it on IE
|
|
switch (r.type || 1) {
|
|
// Rule
|
|
case 1:
|
|
if (r.selectorText) {
|
|
each(r.selectorText.split(','), function(v) {
|
|
v = v.replace(/^\s*|\s*$|^\s\./g, "");
|
|
|
|
// Is internal or it doesn't contain a class
|
|
if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
|
|
return;
|
|
|
|
// Remove everything but class name
|
|
ov = v;
|
|
v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
|
|
|
|
// Filter classes
|
|
if (f && !(v = f(v, ov)))
|
|
return;
|
|
|
|
if (!lo[v]) {
|
|
cl.push({'class' : v});
|
|
lo[v] = 1;
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
|
|
// Import
|
|
case 3:
|
|
addClasses(r.styleSheet);
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
|
|
try {
|
|
each(t.doc.styleSheets, addClasses);
|
|
} catch (ex) {
|
|
// Ignore
|
|
}
|
|
|
|
if (cl.length > 0)
|
|
t.classes = cl;
|
|
|
|
return cl;
|
|
},
|
|
|
|
run : function(e, f, s) {
|
|
var t = this, o;
|
|
|
|
if (t.doc && typeof(e) === 'string')
|
|
e = t.get(e);
|
|
|
|
if (!e)
|
|
return false;
|
|
|
|
s = s || this;
|
|
if (!e.nodeType && (e.length || e.length === 0)) {
|
|
o = [];
|
|
|
|
each(e, function(e, i) {
|
|
if (e) {
|
|
if (typeof(e) == 'string')
|
|
e = t.doc.getElementById(e);
|
|
|
|
o.push(f.call(s, e, i));
|
|
}
|
|
});
|
|
|
|
return o;
|
|
}
|
|
|
|
return f.call(s, e);
|
|
},
|
|
|
|
getAttribs : function(n) {
|
|
var o;
|
|
|
|
n = this.get(n);
|
|
|
|
if (!n)
|
|
return [];
|
|
|
|
if (isIE) {
|
|
o = [];
|
|
|
|
// Object will throw exception in IE
|
|
if (n.nodeName == 'OBJECT')
|
|
return n.attributes;
|
|
|
|
// IE doesn't keep the selected attribute if you clone option elements
|
|
if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
|
|
o.push({specified : 1, nodeName : 'selected'});
|
|
|
|
// It's crazy that this is faster in IE but it's because it returns all attributes all the time
|
|
n.cloneNode(false).outerHTML.replace(/<\/?[\w:]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=\w+|>/gi, '').replace(/[\w:]+/gi, function(a) {
|
|
o.push({specified : 1, nodeName : a});
|
|
});
|
|
|
|
return o;
|
|
}
|
|
|
|
return n.attributes;
|
|
},
|
|
|
|
destroy : function(s) {
|
|
var t = this;
|
|
|
|
if (t.events)
|
|
t.events.destroy();
|
|
|
|
t.win = t.doc = t.root = t.events = null;
|
|
|
|
// Manual destroy then remove unload handler
|
|
if (!s)
|
|
tinymce.removeUnload(t.destroy);
|
|
},
|
|
|
|
createRng : function() {
|
|
var d = this.doc;
|
|
|
|
return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
|
|
},
|
|
|
|
split : function(pe, e, re) {
|
|
var t = this, r = t.createRng(), bef, aft, pa;
|
|
|
|
// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sence
|
|
// but we don't want that in our code since it serves no purpose
|
|
// For example if this is chopped:
|
|
// <p>text 1<span><b>CHOP</b></span>text 2</p>
|
|
// would produce:
|
|
// <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
|
|
// this function will then trim of empty edges and produce:
|
|
// <p>text 1</p><b>CHOP</b><p>text 2</p>
|
|
function trimEdge(n, na) {
|
|
n = n[na];
|
|
|
|
if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na]))
|
|
t.remove(n[na]);
|
|
};
|
|
|
|
function isEmpty(n) {
|
|
n = t.getOuterHTML(n);
|
|
n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars
|
|
n = n.replace(/<[^>]+>/g, ''); // Remove all tags
|
|
|
|
return n.replace(/[ \t\r\n]+| | /g, '') == '';
|
|
};
|
|
|
|
// Added until Gecko can create real HTML documents using implementation.createHTMLDocument
|
|
// this is to future proof it if Gecko decides to implement the error checking for range methods.
|
|
function nodeIndex(n) {
|
|
var i = 0;
|
|
|
|
while (n.previousSibling) {
|
|
i++;
|
|
n = n.previousSibling;
|
|
}
|
|
|
|
return i;
|
|
};
|
|
|
|
if (pe && e) {
|
|
// Get before chunk
|
|
r.setStart(pe.parentNode, nodeIndex(pe));
|
|
r.setEnd(e.parentNode, nodeIndex(e));
|
|
bef = r.extractContents();
|
|
|
|
// Get after chunk
|
|
r = t.createRng();
|
|
r.setStart(e.parentNode, nodeIndex(e) + 1);
|
|
r.setEnd(pe.parentNode, nodeIndex(pe) + 1);
|
|
aft = r.extractContents();
|
|
|
|
// Insert chunks and remove parent
|
|
pa = pe.parentNode;
|
|
|
|
// Remove right side edge of the before contents
|
|
trimEdge(bef, 'lastChild');
|
|
|
|
if (!isEmpty(bef))
|
|
pa.insertBefore(bef, pe);
|
|
|
|
if (re)
|
|
pa.replaceChild(re, e);
|
|
else
|
|
pa.insertBefore(e, pe);
|
|
|
|
// Remove left site edge of the after contents
|
|
trimEdge(aft, 'firstChild');
|
|
|
|
if (!isEmpty(aft))
|
|
pa.insertBefore(aft, pe);
|
|
|
|
t.remove(pe);
|
|
|
|
return re || e;
|
|
}
|
|
},
|
|
|
|
bind : function(target, name, func, scope) {
|
|
var t = this;
|
|
|
|
if (!t.events)
|
|
t.events = new tinymce.dom.EventUtils();
|
|
|
|
return t.events.add(target, name, func, scope || this);
|
|
},
|
|
|
|
unbind : function(target, name, func) {
|
|
var t = this;
|
|
|
|
if (!t.events)
|
|
t.events = new tinymce.dom.EventUtils();
|
|
|
|
return t.events.remove(target, name, func);
|
|
},
|
|
|
|
|
|
_findSib : function(node, selector, name) {
|
|
var t = this, f = selector;
|
|
|
|
if (node) {
|
|
// If expression make a function of it using is
|
|
if (is(f, 'string')) {
|
|
f = function(node) {
|
|
return t.is(node, selector);
|
|
};
|
|
}
|
|
|
|
// Loop all siblings
|
|
for (node = node[name]; node; node = node[name]) {
|
|
if (f(node))
|
|
return node;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
_isRes : function(c) {
|
|
// Is live resizble element
|
|
return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
|
|
}
|
|
|
|
/*
|
|
walk : function(n, f, s) {
|
|
var d = this.doc, w;
|
|
|
|
if (d.createTreeWalker) {
|
|
w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
|
|
|
|
while ((n = w.nextNode()) != null)
|
|
f.call(s || this, n);
|
|
} else
|
|
tinymce.walk(n, f, 'childNodes', s);
|
|
}
|
|
*/
|
|
|
|
/*
|
|
toRGB : function(s) {
|
|
var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
|
|
|
|
if (c) {
|
|
// #FFF -> #FFFFFF
|
|
if (!is(c[3]))
|
|
c[3] = c[2] = c[1];
|
|
|
|
return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
|
|
}
|
|
|
|
return s;
|
|
}
|
|
*/
|
|
});
|
|
|
|
tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
|
|
})(tinymce);
|
|
(function(ns) {
|
|
// Traverse constants
|
|
var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend;
|
|
|
|
function indexOf(child, parent) {
|
|
var i, node;
|
|
|
|
if (child.parentNode != parent)
|
|
return -1;
|
|
|
|
for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling)
|
|
i++;
|
|
|
|
return i;
|
|
};
|
|
|
|
function nodeIndex(n) {
|
|
var i = 0;
|
|
|
|
while (n.previousSibling) {
|
|
i++;
|
|
n = n.previousSibling;
|
|
}
|
|
|
|
return i;
|
|
};
|
|
|
|
function getSelectedNode(container, offset) {
|
|
var child;
|
|
|
|
if (container.nodeType == 3 /* TEXT_NODE */)
|
|
return container;
|
|
|
|
if (offset < 0)
|
|
return container;
|
|
|
|
child = container.firstChild;
|
|
while (child != null && offset > 0) {
|
|
--offset;
|
|
child = child.nextSibling;
|
|
}
|
|
|
|
if (child != null)
|
|
return child;
|
|
|
|
return container;
|
|
};
|
|
|
|
// Range constructor
|
|
function Range(dom) {
|
|
var d = dom.doc;
|
|
|
|
extend(this, {
|
|
dom : dom,
|
|
|
|
// Inital states
|
|
startContainer : d,
|
|
startOffset : 0,
|
|
endContainer : d,
|
|
endOffset : 0,
|
|
collapsed : true,
|
|
commonAncestorContainer : d,
|
|
|
|
// Range constants
|
|
START_TO_START : 0,
|
|
START_TO_END : 1,
|
|
END_TO_END : 2,
|
|
END_TO_START : 3
|
|
});
|
|
};
|
|
|
|
// Add range methods
|
|
extend(Range.prototype, {
|
|
setStart : function(n, o) {
|
|
this._setEndPoint(true, n, o);
|
|
},
|
|
|
|
setEnd : function(n, o) {
|
|
this._setEndPoint(false, n, o);
|
|
},
|
|
|
|
setStartBefore : function(n) {
|
|
this.setStart(n.parentNode, nodeIndex(n));
|
|
},
|
|
|
|
setStartAfter : function(n) {
|
|
this.setStart(n.parentNode, nodeIndex(n) + 1);
|
|
},
|
|
|
|
setEndBefore : function(n) {
|
|
this.setEnd(n.parentNode, nodeIndex(n));
|
|
},
|
|
|
|
setEndAfter : function(n) {
|
|
this.setEnd(n.parentNode, nodeIndex(n) + 1);
|
|
},
|
|
|
|
collapse : function(ts) {
|
|
var t = this;
|
|
|
|
if (ts) {
|
|
t.endContainer = t.startContainer;
|
|
t.endOffset = t.startOffset;
|
|
} else {
|
|
t.startContainer = t.endContainer;
|
|
t.startOffset = t.endOffset;
|
|
}
|
|
|
|
t.collapsed = true;
|
|
},
|
|
|
|
selectNode : function(n) {
|
|
this.setStartBefore(n);
|
|
this.setEndAfter(n);
|
|
},
|
|
|
|
selectNodeContents : function(n) {
|
|
this.setStart(n, 0);
|
|
this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
|
|
},
|
|
|
|
compareBoundaryPoints : function(h, r) {
|
|
var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset;
|
|
|
|
// Check START_TO_START
|
|
if (h === 0)
|
|
return t._compareBoundaryPoints(sc, so, sc, so);
|
|
|
|
// Check START_TO_END
|
|
if (h === 1)
|
|
return t._compareBoundaryPoints(sc, so, ec, eo);
|
|
|
|
// Check END_TO_END
|
|
if (h === 2)
|
|
return t._compareBoundaryPoints(ec, eo, ec, eo);
|
|
|
|
// Check END_TO_START
|
|
if (h === 3)
|
|
return t._compareBoundaryPoints(ec, eo, sc, so);
|
|
},
|
|
|
|
deleteContents : function() {
|
|
this._traverse(DELETE);
|
|
},
|
|
|
|
extractContents : function() {
|
|
return this._traverse(EXTRACT);
|
|
},
|
|
|
|
cloneContents : function() {
|
|
return this._traverse(CLONE);
|
|
},
|
|
|
|
insertNode : function(n) {
|
|
var t = this, nn, o;
|
|
|
|
// Node is TEXT_NODE or CDATA
|
|
if (n.nodeType === 3 || n.nodeType === 4) {
|
|
nn = t.startContainer.splitText(t.startOffset);
|
|
t.startContainer.parentNode.insertBefore(n, nn);
|
|
} else {
|
|
// Insert element node
|
|
if (t.startContainer.childNodes.length > 0)
|
|
o = t.startContainer.childNodes[t.startOffset];
|
|
|
|
t.startContainer.insertBefore(n, o);
|
|
}
|
|
},
|
|
|
|
surroundContents : function(n) {
|
|
var t = this, f = t.extractContents();
|
|
|
|
t.insertNode(n);
|
|
n.appendChild(f);
|
|
t.selectNode(n);
|
|
},
|
|
|
|
cloneRange : function() {
|
|
var t = this;
|
|
|
|
return extend(new Range(t.dom), {
|
|
startContainer : t.startContainer,
|
|
startOffset : t.startOffset,
|
|
endContainer : t.endContainer,
|
|
endOffset : t.endOffset,
|
|
collapsed : t.collapsed,
|
|
commonAncestorContainer : t.commonAncestorContainer
|
|
});
|
|
},
|
|
|
|
/*
|
|
detach : function() {
|
|
// Not implemented
|
|
},
|
|
*/
|
|
// Internal methods
|
|
|
|
_isCollapsed : function() {
|
|
return (this.startContainer == this.endContainer && this.startOffset == this.endOffset);
|
|
},
|
|
|
|
_compareBoundaryPoints : function (containerA, offsetA, containerB, offsetB) {
|
|
var c, offsetC, n, cmnRoot, childA, childB;
|
|
|
|
// In the first case the boundary-points have the same container. A is before B
|
|
// if its offset is less than the offset of B, A is equal to B if its offset is
|
|
// equal to the offset of B, and A is after B if its offset is greater than the
|
|
// offset of B.
|
|
if (containerA == containerB) {
|
|
if (offsetA == offsetB) {
|
|
return 0; // equal
|
|
} else if (offsetA < offsetB) {
|
|
return -1; // before
|
|
} else {
|
|
return 1; // after
|
|
}
|
|
}
|
|
|
|
// In the second case a child node C of the container of A is an ancestor
|
|
// container of B. In this case, A is before B if the offset of A is less than or
|
|
// equal to the index of the child node C and A is after B otherwise.
|
|
c = containerB;
|
|
while (c && c.parentNode != containerA) {
|
|
c = c.parentNode;
|
|
}
|
|
if (c) {
|
|
offsetC = 0;
|
|
n = containerA.firstChild;
|
|
|
|
while (n != c && offsetC < offsetA) {
|
|
offsetC++;
|
|
n = n.nextSibling;
|
|
}
|
|
|
|
if (offsetA <= offsetC) {
|
|
return -1; // before
|
|
} else {
|
|
return 1; // after
|
|
}
|
|
}
|
|
|
|
// In the third case a child node C of the container of B is an ancestor container
|
|
// of A. In this case, A is before B if the index of the child node C is less than
|
|
// the offset of B and A is after B otherwise.
|
|
c = containerA;
|
|
while (c && c.parentNode != containerB) {
|
|
c = c.parentNode;
|
|
}
|
|
|
|
if (c) {
|
|
offsetC = 0;
|
|
n = containerB.firstChild;
|
|
|
|
while (n != c && offsetC < offsetB) {
|
|
offsetC++;
|
|
n = n.nextSibling;
|
|
}
|
|
|
|
if (offsetC < offsetB) {
|
|
return -1; // before
|
|
} else {
|
|
return 1; // after
|
|
}
|
|
}
|
|
|
|
// In the fourth case, none of three other cases hold: the containers of A and B
|
|
// are siblings or descendants of sibling nodes. In this case, A is before B if
|
|
// the container of A is before the container of B in a pre-order traversal of the
|
|
// Ranges' context tree and A is after B otherwise.
|
|
cmnRoot = this.dom.findCommonAncestor(containerA, containerB);
|
|
childA = containerA;
|
|
|
|
while (childA && childA.parentNode != cmnRoot) {
|
|
childA = childA.parentNode;
|
|
}
|
|
|
|
if (!childA) {
|
|
childA = cmnRoot;
|
|
}
|
|
|
|
childB = containerB;
|
|
while (childB && childB.parentNode != cmnRoot) {
|
|
childB = childB.parentNode;
|
|
}
|
|
|
|
if (!childB) {
|
|
childB = cmnRoot;
|
|
}
|
|
|
|
if (childA == childB) {
|
|
return 0; // equal
|
|
}
|
|
|
|
n = cmnRoot.firstChild;
|
|
while (n) {
|
|
if (n == childA) {
|
|
return -1; // before
|
|
}
|
|
|
|
if (n == childB) {
|
|
return 1; // after
|
|
}
|
|
|
|
n = n.nextSibling;
|
|
}
|
|
},
|
|
|
|
_setEndPoint : function(st, n, o) {
|
|
var t = this, ec, sc;
|
|
|
|
if (st) {
|
|
t.startContainer = n;
|
|
t.startOffset = o;
|
|
} else {
|
|
t.endContainer = n;
|
|
t.endOffset = o;
|
|
}
|
|
|
|
// If one boundary-point of a Range is set to have a root container
|
|
// other than the current one for the Range, the Range is collapsed to
|
|
// the new position. This enforces the restriction that both boundary-
|
|
// points of a Range must have the same root container.
|
|
ec = t.endContainer;
|
|
while (ec.parentNode)
|
|
ec = ec.parentNode;
|
|
|
|
sc = t.startContainer;
|
|
while (sc.parentNode)
|
|
sc = sc.parentNode;
|
|
|
|
if (sc != ec) {
|
|
t.collapse(st);
|
|
} else {
|
|
// The start position of a Range is guaranteed to never be after the
|
|
// end position. To enforce this restriction, if the start is set to
|
|
// be at a position after the end, the Range is collapsed to that
|
|
// position.
|
|
if (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0)
|
|
t.collapse(st);
|
|
}
|
|
|
|
t.collapsed = t._isCollapsed();
|
|
t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer);
|
|
},
|
|
|
|
// This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :)
|
|
|
|
_traverse : function(how) {
|
|
var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
|
|
|
|
if (t.startContainer == t.endContainer)
|
|
return t._traverseSameContainer(how);
|
|
|
|
for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) {
|
|
if (p == t.startContainer)
|
|
return t._traverseCommonStartContainer(c, how);
|
|
|
|
++endContainerDepth;
|
|
}
|
|
|
|
for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) {
|
|
if (p == t.endContainer)
|
|
return t._traverseCommonEndContainer(c, how);
|
|
|
|
++startContainerDepth;
|
|
}
|
|
|
|
depthDiff = startContainerDepth - endContainerDepth;
|
|
|
|
startNode = t.startContainer;
|
|
while (depthDiff > 0) {
|
|
startNode = startNode.parentNode;
|
|
depthDiff--;
|
|
}
|
|
|
|
endNode = t.endContainer;
|
|
while (depthDiff < 0) {
|
|
endNode = endNode.parentNode;
|
|
depthDiff++;
|
|
}
|
|
|
|
// ascend the ancestor hierarchy until we have a common parent.
|
|
for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
|
|
startNode = sp;
|
|
endNode = ep;
|
|
}
|
|
|
|
return t._traverseCommonAncestors(startNode, endNode, how);
|
|
},
|
|
|
|
_traverseSameContainer : function(how) {
|
|
var t = this, frag, s, sub, n, cnt, sibling, xferNode;
|
|
|
|
if (how != DELETE)
|
|
frag = t.dom.doc.createDocumentFragment();
|
|
|
|
// If selection is empty, just return the fragment
|
|
if (t.startOffset == t.endOffset)
|
|
return frag;
|
|
|
|
// Text node needs special case handling
|
|
if (t.startContainer.nodeType == 3 /* TEXT_NODE */) {
|
|
// get the substring
|
|
s = t.startContainer.nodeValue;
|
|
sub = s.substring(t.startOffset, t.endOffset);
|
|
|
|
// set the original text node to its new value
|
|
if (how != CLONE) {
|
|
t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset);
|
|
|
|
// Nothing is partially selected, so collapse to start point
|
|
t.collapse(true);
|
|
}
|
|
|
|
if (how == DELETE)
|
|
return null;
|
|
|
|
frag.appendChild(t.dom.doc.createTextNode(sub));
|
|
return frag;
|
|
}
|
|
|
|
// Copy nodes between the start/end offsets.
|
|
n = getSelectedNode(t.startContainer, t.startOffset);
|
|
cnt = t.endOffset - t.startOffset;
|
|
|
|
while (cnt > 0) {
|
|
sibling = n.nextSibling;
|
|
xferNode = t._traverseFullySelected(n, how);
|
|
|
|
if (frag)
|
|
frag.appendChild( xferNode );
|
|
|
|
--cnt;
|
|
n = sibling;
|
|
}
|
|
|
|
// Nothing is partially selected, so collapse to start point
|
|
if (how != CLONE)
|
|
t.collapse(true);
|
|
|
|
return frag;
|
|
},
|
|
|
|
_traverseCommonStartContainer : function(endAncestor, how) {
|
|
var t = this, frag, n, endIdx, cnt, sibling, xferNode;
|
|
|
|
if (how != DELETE)
|
|
frag = t.dom.doc.createDocumentFragment();
|
|
|
|
n = t._traverseRightBoundary(endAncestor, how);
|
|
|
|
if (frag)
|
|
frag.appendChild(n);
|
|
|
|
endIdx = indexOf(endAncestor, t.startContainer);
|
|
cnt = endIdx - t.startOffset;
|
|
|
|
if (cnt <= 0) {
|
|
// Collapse to just before the endAncestor, which
|
|
// is partially selected.
|
|
if (how != CLONE) {
|
|
t.setEndBefore(endAncestor);
|
|
t.collapse(false);
|
|
}
|
|
|
|
return frag;
|
|
}
|
|
|
|
n = endAncestor.previousSibling;
|
|
while (cnt > 0) {
|
|
sibling = n.previousSibling;
|
|
xferNode = t._traverseFullySelected(n, how);
|
|
|
|
if (frag)
|
|
frag.insertBefore(xferNode, frag.firstChild);
|
|
|
|
--cnt;
|
|
n = sibling;
|
|
}
|
|
|
|
// Collapse to just before the endAncestor, which
|
|
// is partially selected.
|
|
if (how != CLONE) {
|
|
t.setEndBefore(endAncestor);
|
|
t.collapse(false);
|
|
}
|
|
|
|
return frag;
|
|
},
|
|
|
|
_traverseCommonEndContainer : function(startAncestor, how) {
|
|
var t = this, frag, startIdx, n, cnt, sibling, xferNode;
|
|
|
|
if (how != DELETE)
|
|
frag = t.dom.doc.createDocumentFragment();
|
|
|
|
n = t._traverseLeftBoundary(startAncestor, how);
|
|
if (frag)
|
|
frag.appendChild(n);
|
|
|
|
startIdx = indexOf(startAncestor, t.endContainer);
|
|
++startIdx; // Because we already traversed it....
|
|
|
|
cnt = t.endOffset - startIdx;
|
|
n = startAncestor.nextSibling;
|
|
while (cnt > 0) {
|
|
sibling = n.nextSibling;
|
|
xferNode = t._traverseFullySelected(n, how);
|
|
|
|
if (frag)
|
|
frag.appendChild(xferNode);
|
|
|
|
--cnt;
|
|
n = sibling;
|
|
}
|
|
|
|
if (how != CLONE) {
|
|
t.setStartAfter(startAncestor);
|
|
t.collapse(true);
|
|
}
|
|
|
|
return frag;
|
|
},
|
|
|
|
_traverseCommonAncestors : function(startAncestor, endAncestor, how) {
|
|
var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
|
|
|
|
if (how != DELETE)
|
|
frag = t.dom.doc.createDocumentFragment();
|
|
|
|
n = t._traverseLeftBoundary(startAncestor, how);
|
|
if (frag)
|
|
frag.appendChild(n);
|
|
|
|
commonParent = startAncestor.parentNode;
|
|
startOffset = indexOf(startAncestor, commonParent);
|
|
endOffset = indexOf(endAncestor, commonParent);
|
|
++startOffset;
|
|
|
|
cnt = endOffset - startOffset;
|
|
sibling = startAncestor.nextSibling;
|
|
|
|
while (cnt > 0) {
|
|
nextSibling = sibling.nextSibling;
|
|
n = t._traverseFullySelected(sibling, how);
|
|
|
|
if (frag)
|
|
frag.appendChild(n);
|
|
|
|
sibling = nextSibling;
|
|
--cnt;
|
|
}
|
|
|
|
n = t._traverseRightBoundary(endAncestor, how);
|
|
|
|
if (frag)
|
|
frag.appendChild(n);
|
|
|
|
if (how != CLONE) {
|
|
t.setStartAfter(startAncestor);
|
|
t.collapse(true);
|
|
}
|
|
|
|
return frag;
|
|
},
|
|
|
|
_traverseRightBoundary : function(root, how) {
|
|
var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent;
|
|
var isFullySelected = next != t.endContainer;
|
|
|
|
if (next == root)
|
|
return t._traverseNode(next, isFullySelected, false, how);
|
|
|
|
parent = next.parentNode;
|
|
clonedParent = t._traverseNode(parent, false, false, how);
|
|
|
|
while (parent != null) {
|
|
while (next != null) {
|
|
prevSibling = next.previousSibling;
|
|
clonedChild = t._traverseNode(next, isFullySelected, false, how);
|
|
|
|
if (how != DELETE)
|
|
clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
|
|
|
|
isFullySelected = true;
|
|
next = prevSibling;
|
|
}
|
|
|
|
if (parent == root)
|
|
return clonedParent;
|
|
|
|
next = parent.previousSibling;
|
|
parent = parent.parentNode;
|
|
|
|
clonedGrandParent = t._traverseNode(parent, false, false, how);
|
|
|
|
if (how != DELETE)
|
|
clonedGrandParent.appendChild(clonedParent);
|
|
|
|
clonedParent = clonedGrandParent;
|
|
}
|
|
|
|
// should never occur
|
|
return null;
|
|
},
|
|
|
|
_traverseLeftBoundary : function(root, how) {
|
|
var t = this, next = getSelectedNode(t.startContainer, t.startOffset);
|
|
var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
|
|
|
|
if (next == root)
|
|
return t._traverseNode(next, isFullySelected, true, how);
|
|
|
|
parent = next.parentNode;
|
|
clonedParent = t._traverseNode(parent, false, true, how);
|
|
|
|
while (parent != null) {
|
|
while (next != null) {
|
|
nextSibling = next.nextSibling;
|
|
clonedChild = t._traverseNode(next, isFullySelected, true, how);
|
|
|
|
if (how != DELETE)
|
|
clonedParent.appendChild(clonedChild);
|
|
|
|
isFullySelected = true;
|
|
next = nextSibling;
|
|
}
|
|
|
|
if (parent == root)
|
|
return clonedParent;
|
|
|
|
next = parent.nextSibling;
|
|
parent = parent.parentNode;
|
|
|
|
clonedGrandParent = t._traverseNode(parent, false, true, how);
|
|
|
|
if (how != DELETE)
|
|
clonedGrandParent.appendChild(clonedParent);
|
|
|
|
clonedParent = clonedGrandParent;
|
|
}
|
|
|
|
// should never occur
|
|
return null;
|
|
},
|
|
|
|
_traverseNode : function(n, isFullySelected, isLeft, how) {
|
|
var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode;
|
|
|
|
if (isFullySelected)
|
|
return t._traverseFullySelected(n, how);
|
|
|
|
if (n.nodeType == 3 /* TEXT_NODE */) {
|
|
txtValue = n.nodeValue;
|
|
|
|
if (isLeft) {
|
|
offset = t.startOffset;
|
|
newNodeValue = txtValue.substring(offset);
|
|
oldNodeValue = txtValue.substring(0, offset);
|
|
} else {
|
|
offset = t.endOffset;
|
|
newNodeValue = txtValue.substring(0, offset);
|
|
oldNodeValue = txtValue.substring(offset);
|
|
}
|
|
|
|
if (how != CLONE)
|
|
n.nodeValue = oldNodeValue;
|
|
|
|
if (how == DELETE)
|
|
return null;
|
|
|
|
newNode = n.cloneNode(false);
|
|
newNode.nodeValue = newNodeValue;
|
|
|
|
return newNode;
|
|
}
|
|
|
|
if (how == DELETE)
|
|
return null;
|
|
|
|
return n.cloneNode(false);
|
|
},
|
|
|
|
_traverseFullySelected : function(n, how) {
|
|
var t = this;
|
|
|
|
if (how != DELETE)
|
|
return how == CLONE ? n.cloneNode(true) : n;
|
|
|
|
n.parentNode.removeChild(n);
|
|
return null;
|
|
}
|
|
});
|
|
|
|
ns.Range = Range;
|
|
})(tinymce.dom);
|
|
(function() {
|
|
function Selection(selection) {
|
|
var t = this, invisibleChar = '\uFEFF', range, lastIERng;
|
|
|
|
function compareRanges(rng1, rng2) {
|
|
if (rng1 && rng2) {
|
|
// Both are control ranges and the selected element matches
|
|
if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
|
|
return 1;
|
|
|
|
// Both are text ranges and the range matches
|
|
if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
function getRange() {
|
|
var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed;
|
|
|
|
function findIndex(element) {
|
|
var nl = element.parentNode.childNodes, i;
|
|
|
|
for (i = nl.length - 1; i >= 0; i--) {
|
|
if (nl[i] == element)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
function findEndPoint(start) {
|
|
var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng;
|
|
|
|
// Insert marker character
|
|
rng.collapse(start);
|
|
parent = rng.parentElement();
|
|
rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue
|
|
|
|
// Find marker character
|
|
nl = parent.childNodes;
|
|
for (i = 0; i < nl.length; i++) {
|
|
n = nl[i];
|
|
|
|
// Calculate node index excluding text node fragmentation
|
|
if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3))
|
|
index++;
|
|
|
|
// If text node then calculate offset
|
|
if (n.nodeType === 3) {
|
|
// Look for marker
|
|
pos = n.nodeValue.indexOf(invisibleChar);
|
|
if (pos !== -1) {
|
|
offset += pos;
|
|
break;
|
|
}
|
|
|
|
offset += n.nodeValue.length;
|
|
} else
|
|
offset = 0;
|
|
}
|
|
|
|
// Remove marker character
|
|
rng.moveStart('character', -1);
|
|
rng.text = '';
|
|
|
|
return {index : index, offset : offset, parent : parent};
|
|
};
|
|
|
|
// If selection is outside the current document just return an empty range
|
|
element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
|
|
if (element.ownerDocument != dom.doc)
|
|
return domRange;
|
|
|
|
// Handle control selection or text selection of a image
|
|
if (ieRange.item || !element.hasChildNodes()) {
|
|
domRange.setStart(element.parentNode, findIndex(element));
|
|
domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
|
|
|
|
return domRange;
|
|
}
|
|
|
|
// Check collapsed state
|
|
collapsed = selection.isCollapsed();
|
|
|
|
// Find start and end pos index and offset
|
|
startPos = findEndPoint(true);
|
|
endPos = findEndPoint(false);
|
|
|
|
// Normalize the elements to avoid fragmented dom
|
|
startPos.parent.normalize();
|
|
endPos.parent.normalize();
|
|
|
|
// Set start container and offset
|
|
sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)];
|
|
|
|
if (sc.nodeType != 3)
|
|
domRange.setStart(startPos.parent, startPos.index);
|
|
else
|
|
domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset);
|
|
|
|
// Set end container and offset
|
|
ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)];
|
|
|
|
if (ec.nodeType != 3) {
|
|
if (!collapsed)
|
|
endPos.index++;
|
|
|
|
domRange.setEnd(endPos.parent, endPos.index);
|
|
} else
|
|
domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset);
|
|
|
|
// If not collapsed then make sure offsets are valid
|
|
if (!collapsed) {
|
|
sc = domRange.startContainer;
|
|
if (sc.nodeType == 1)
|
|
domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length));
|
|
|
|
ec = domRange.endContainer;
|
|
if (ec.nodeType == 1)
|
|
domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length));
|
|
}
|
|
|
|
// Restore selection to new range
|
|
t.addRange(domRange);
|
|
|
|
return domRange;
|
|
};
|
|
|
|
this.addRange = function(rng) {
|
|
var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo;
|
|
|
|
// Setup some shorter versions
|
|
sc = rng.startContainer;
|
|
so = rng.startOffset;
|
|
ec = rng.endContainer;
|
|
eo = rng.endOffset;
|
|
ieRng = body.createTextRange();
|
|
|
|
// Find element
|
|
sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc;
|
|
ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec;
|
|
|
|
// Single element selection
|
|
if (sc == ec && sc.nodeType == 1) {
|
|
// Make control selection for some elements
|
|
if (/^(IMG|TABLE)$/.test(sc.nodeName) && so != eo) {
|
|
ieRng = body.createControlRange();
|
|
ieRng.addElement(sc);
|
|
} else {
|
|
ieRng = body.createTextRange();
|
|
|
|
// Padd empty elements with invisible character
|
|
if (!sc.hasChildNodes() && sc.canHaveHTML)
|
|
sc.innerHTML = invisibleChar;
|
|
|
|
// Select element contents
|
|
ieRng.moveToElementText(sc);
|
|
|
|
// If it's only containing a padding remove it so the caret remains
|
|
if (sc.innerHTML == invisibleChar) {
|
|
ieRng.collapse(true);
|
|
sc.removeChild(sc.firstChild);
|
|
}
|
|
}
|
|
|
|
if (so == eo)
|
|
ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1);
|
|
|
|
ieRng.select();
|
|
|
|
return;
|
|
}
|
|
|
|
function getCharPos(container, offset) {
|
|
var nodeVal, rng, pos;
|
|
|
|
if (container.nodeType != 3)
|
|
return -1;
|
|
|
|
nodeVal = container.nodeValue;
|
|
rng = body.createTextRange();
|
|
|
|
// Insert marker at offset position
|
|
container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset);
|
|
|
|
// Find char pos of marker and remove it
|
|
rng.moveToElementText(container.parentNode);
|
|
rng.findText(invisibleChar);
|
|
pos = Math.abs(rng.moveStart('character', -0xFFFFF));
|
|
container.nodeValue = nodeVal;
|
|
|
|
return pos;
|
|
};
|
|
|
|
// Collapsed range
|
|
if (rng.collapsed) {
|
|
pos = getCharPos(sc, so);
|
|
|
|
ieRng = body.createTextRange();
|
|
ieRng.move('character', pos);
|
|
ieRng.select();
|
|
|
|
return;
|
|
} else {
|
|
// If same text container
|
|
if (sc == ec && sc.nodeType == 3) {
|
|
startPos = getCharPos(sc, so);
|
|
|
|
ieRng = body.createTextRange();
|
|
ieRng.move('character', startPos);
|
|
ieRng.moveEnd('character', eo - so);
|
|
ieRng.select();
|
|
|
|
return;
|
|
}
|
|
|
|
// Get caret positions
|
|
startPos = getCharPos(sc, so);
|
|
endPos = getCharPos(ec, eo);
|
|
ieRng = body.createTextRange();
|
|
|
|
// Move start of range to start character position or start element
|
|
if (startPos == -1) {
|
|
ieRng.moveToElementText(sc);
|
|
startPos = 0;
|
|
} else
|
|
ieRng.move('character', startPos);
|
|
|
|
// Move end of range to end character position or end element
|
|
tmpRng = body.createTextRange();
|
|
|
|
if (endPos == -1)
|
|
tmpRng.moveToElementText(ec);
|
|
else
|
|
tmpRng.move('character', endPos);
|
|
|
|
ieRng.setEndPoint('EndToEnd', tmpRng);
|
|
ieRng.select();
|
|
|
|
return;
|
|
}
|
|
};
|
|
|
|
this.getRangeAt = function() {
|
|
// Setup new range if the cache is empty
|
|
if (!range || !compareRanges(lastIERng, selection.getRng())) {
|
|
range = getRange();
|
|
|
|
// Store away text range for next call
|
|
lastIERng = selection.getRng();
|
|
}
|
|
|
|
// Return cached range
|
|
return range;
|
|
};
|
|
|
|
this.destroy = function() {
|
|
// Destroy cached range and last IE range to avoid memory leaks
|
|
lastIERng = range = null;
|
|
};
|
|
};
|
|
|
|
// Expose the selection object
|
|
tinymce.dom.TridentSelection = Selection;
|
|
})();
|
|
|
|
/*
|
|
* Sizzle CSS Selector Engine - v1.0
|
|
* Copyright 2009, The Dojo Foundation
|
|
* Released under the MIT, BSD, and GPL Licenses.
|
|
* More information: http://sizzlejs.com/
|
|
*/
|
|
(function(){
|
|
|
|
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
|
|
done = 0,
|
|
toString = Object.prototype.toString,
|
|
hasDuplicate = false;
|
|
|
|
var Sizzle = function(selector, context, results, seed) {
|
|
results = results || [];
|
|
var origContext = context = context || document;
|
|
|
|
if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
|
|
return [];
|
|
}
|
|
|
|
if ( !selector || typeof selector !== "string" ) {
|
|
return results;
|
|
}
|
|
|
|
var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context);
|
|
|
|
// Reset the position of the chunker regexp (start from head)
|
|
chunker.lastIndex = 0;
|
|
|
|
while ( (m = chunker.exec(selector)) !== null ) {
|
|
parts.push( m[1] );
|
|
|
|
if ( m[2] ) {
|
|
extra = RegExp.rightContext;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( parts.length > 1 && origPOS.exec( selector ) ) {
|
|
if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
|
|
set = posProcess( parts[0] + parts[1], context );
|
|
} else {
|
|
set = Expr.relative[ parts[0] ] ?
|
|
[ context ] :
|
|
Sizzle( parts.shift(), context );
|
|
|
|
while ( parts.length ) {
|
|
selector = parts.shift();
|
|
|
|
if ( Expr.relative[ selector ] )
|
|
selector += parts.shift();
|
|
|
|
set = posProcess( selector, set );
|
|
}
|
|
}
|
|
} else {
|
|
// Take a shortcut and set the context if the root selector is an ID
|
|
// (but not if it'll be faster if the inner selector is an ID)
|
|
if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
|
|
Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
|
|
var ret = Sizzle.find( parts.shift(), context, contextXML );
|
|
context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
|
|
}
|
|
|
|
if ( context ) {
|
|
var ret = seed ?
|
|
{ expr: parts.pop(), set: makeArray(seed) } :
|
|
Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
|
|
set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
|
|
|
|
if ( parts.length > 0 ) {
|
|
checkSet = makeArray(set);
|
|
} else {
|
|
prune = false;
|
|
}
|
|
|
|
while ( parts.length ) {
|
|
var cur = parts.pop(), pop = cur;
|
|
|
|
if ( !Expr.relative[ cur ] ) {
|
|
cur = "";
|
|
} else {
|
|
pop = parts.pop();
|
|
}
|
|
|
|
if ( pop == null ) {
|
|
pop = context;
|
|
}
|
|
|
|
Expr.relative[ cur ]( checkSet, pop, contextXML );
|
|
}
|
|
} else {
|
|
checkSet = parts = [];
|
|
}
|
|
}
|
|
|
|
if ( !checkSet ) {
|
|
checkSet = set;
|
|
}
|
|
|
|
if ( !checkSet ) {
|
|
throw "Syntax error, unrecognized expression: " + (cur || selector);
|
|
}
|
|
|
|
if ( toString.call(checkSet) === "[object Array]" ) {
|
|
if ( !prune ) {
|
|
results.push.apply( results, checkSet );
|
|
} else if ( context && context.nodeType === 1 ) {
|
|
for ( var i = 0; checkSet[i] != null; i++ ) {
|
|
if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
|
|
results.push( set[i] );
|
|
}
|
|
}
|
|
} else {
|
|
for ( var i = 0; checkSet[i] != null; i++ ) {
|
|
if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
|
|
results.push( set[i] );
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
makeArray( checkSet, results );
|
|
}
|
|
|
|
if ( extra ) {
|
|
Sizzle( extra, origContext, results, seed );
|
|
Sizzle.uniqueSort( results );
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
Sizzle.uniqueSort = function(results){
|
|
if ( sortOrder ) {
|
|
hasDuplicate = false;
|
|
results.sort(sortOrder);
|
|
|
|
if ( hasDuplicate ) {
|
|
for ( var i = 1; i < results.length; i++ ) {
|
|
if ( results[i] === results[i-1] ) {
|
|
results.splice(i--, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Sizzle.matches = function(expr, set){
|
|
return Sizzle(expr, null, null, set);
|
|
};
|
|
|
|
Sizzle.find = function(expr, context, isXML){
|
|
var set, match;
|
|
|
|
if ( !expr ) {
|
|
return [];
|
|
}
|
|
|
|
for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
|
|
var type = Expr.order[i], match;
|
|
|
|
if ( (match = Expr.match[ type ].exec( expr )) ) {
|
|
var left = RegExp.leftContext;
|
|
|
|
if ( left.substr( left.length - 1 ) !== "\\" ) {
|
|
match[1] = (match[1] || "").replace(/\\/g, "");
|
|
set = Expr.find[ type ]( match, context, isXML );
|
|
if ( set != null ) {
|
|
expr = expr.replace( Expr.match[ type ], "" );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !set ) {
|
|
set = context.getElementsByTagName("*");
|
|
}
|
|
|
|
return {set: set, expr: expr};
|
|
};
|
|
|
|
Sizzle.filter = function(expr, set, inplace, not){
|
|
var old = expr, result = [], curLoop = set, match, anyFound,
|
|
isXMLFilter = set && set[0] && isXML(set[0]);
|
|
|
|
while ( expr && set.length ) {
|
|
for ( var type in Expr.filter ) {
|
|
if ( (match = Expr.match[ type ].exec( expr )) != null ) {
|
|
var filter = Expr.filter[ type ], found, item;
|
|
anyFound = false;
|
|
|
|
if ( curLoop == result ) {
|
|
result = [];
|
|
}
|
|
|
|
if ( Expr.preFilter[ type ] ) {
|
|
match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
|
|
|
|
if ( !match ) {
|
|
anyFound = found = true;
|
|
} else if ( match === true ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( match ) {
|
|
for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
|
|
if ( item ) {
|
|
found = filter( item, match, i, curLoop );
|
|
var pass = not ^ !!found;
|
|
|
|
if ( inplace && found != null ) {
|
|
if ( pass ) {
|
|
anyFound = true;
|
|
} else {
|
|
curLoop[i] = false;
|
|
}
|
|
} else if ( pass ) {
|
|
result.push( item );
|
|
anyFound = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( found !== undefined ) {
|
|
if ( !inplace ) {
|
|
curLoop = result;
|
|
}
|
|
|
|
expr = expr.replace( Expr.match[ type ], "" );
|
|
|
|
if ( !anyFound ) {
|
|
return [];
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Improper expression
|
|
if ( expr == old ) {
|
|
if ( anyFound == null ) {
|
|
throw "Syntax error, unrecognized expression: " + expr;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
old = expr;
|
|
}
|
|
|
|
return curLoop;
|
|
};
|
|
|
|
var Expr = Sizzle.selectors = {
|
|
order: [ "ID", "NAME", "TAG" ],
|
|
match: {
|
|
ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
|
|
CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
|
|
NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
|
|
ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
|
|
TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
|
|
CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
|
|
POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
|
|
PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
|
|
},
|
|
attrMap: {
|
|
"class": "className",
|
|
"for": "htmlFor"
|
|
},
|
|
attrHandle: {
|
|
href: function(elem){
|
|
return elem.getAttribute("href");
|
|
}
|
|
},
|
|
relative: {
|
|
"+": function(checkSet, part, isXML){
|
|
var isPartStr = typeof part === "string",
|
|
isTag = isPartStr && !/\W/.test(part),
|
|
isPartStrNotTag = isPartStr && !isTag;
|
|
|
|
if ( isTag && !isXML ) {
|
|
part = part.toUpperCase();
|
|
}
|
|
|
|
for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
|
|
if ( (elem = checkSet[i]) ) {
|
|
while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
|
|
|
|
checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
|
|
elem || false :
|
|
elem === part;
|
|
}
|
|
}
|
|
|
|
if ( isPartStrNotTag ) {
|
|
Sizzle.filter( part, checkSet, true );
|
|
}
|
|
},
|
|
">": function(checkSet, part, isXML){
|
|
var isPartStr = typeof part === "string";
|
|
|
|
if ( isPartStr && !/\W/.test(part) ) {
|
|
part = isXML ? part : part.toUpperCase();
|
|
|
|
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
|
|
var elem = checkSet[i];
|
|
if ( elem ) {
|
|
var parent = elem.parentNode;
|
|
checkSet[i] = parent.nodeName === part ? parent : false;
|
|
}
|
|
}
|
|
} else {
|
|
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
|
|
var elem = checkSet[i];
|
|
if ( elem ) {
|
|
checkSet[i] = isPartStr ?
|
|
elem.parentNode :
|
|
elem.parentNode === part;
|
|
}
|
|
}
|
|
|
|
if ( isPartStr ) {
|
|
Sizzle.filter( part, checkSet, true );
|
|
}
|
|
}
|
|
},
|
|
"": function(checkSet, part, isXML){
|
|
var doneName = done++, checkFn = dirCheck;
|
|
|
|
if ( !part.match(/\W/) ) {
|
|
var nodeCheck = part = isXML ? part : part.toUpperCase();
|
|
checkFn = dirNodeCheck;
|
|
}
|
|
|
|
checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
|
|
},
|
|
"~": function(checkSet, part, isXML){
|
|
var doneName = done++, checkFn = dirCheck;
|
|
|
|
if ( typeof part === "string" && !part.match(/\W/) ) {
|
|
var nodeCheck = part = isXML ? part : part.toUpperCase();
|
|
checkFn = dirNodeCheck;
|
|
}
|
|
|
|
checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
|
|
}
|
|
},
|
|
find: {
|
|
ID: function(match, context, isXML){
|
|
if ( typeof context.getElementById !== "undefined" && !isXML ) {
|
|
var m = context.getElementById(match[1]);
|
|
return m ? [m] : [];
|
|
}
|
|
},
|
|
NAME: function(match, context, isXML){
|
|
if ( typeof context.getElementsByName !== "undefined" ) {
|
|
var ret = [], results = context.getElementsByName(match[1]);
|
|
|
|
for ( var i = 0, l = results.length; i < l; i++ ) {
|
|
if ( results[i].getAttribute("name") === match[1] ) {
|
|
ret.push( results[i] );
|
|
}
|
|
}
|
|
|
|
return ret.length === 0 ? null : ret;
|
|
}
|
|
},
|
|
TAG: function(match, context){
|
|
return context.getElementsByTagName(match[1]);
|
|
}
|
|
},
|
|
preFilter: {
|
|
CLASS: function(match, curLoop, inplace, result, not, isXML){
|
|
match = " " + match[1].replace(/\\/g, "") + " ";
|
|
|
|
if ( isXML ) {
|
|
return match;
|
|
}
|
|
|
|
for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
|
|
if ( elem ) {
|
|
if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
|
|
if ( !inplace )
|
|
result.push( elem );
|
|
} else if ( inplace ) {
|
|
curLoop[i] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
ID: function(match){
|
|
return match[1].replace(/\\/g, "");
|
|
},
|
|
TAG: function(match, curLoop){
|
|
for ( var i = 0; curLoop[i] === false; i++ ){}
|
|
return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
|
|
},
|
|
CHILD: function(match){
|
|
if ( match[1] == "nth" ) {
|
|
// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
|
|
var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
|
|
match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
|
|
!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
|
|
|
|
// calculate the numbers (first)n+(last) including if they are negative
|
|
match[2] = (test[1] + (test[2] || 1)) - 0;
|
|
match[3] = test[3] - 0;
|
|
}
|
|
|
|
// TODO: Move to normal caching system
|
|
match[0] = done++;
|
|
|
|
return match;
|
|
},
|
|
ATTR: function(match, curLoop, inplace, result, not, isXML){
|
|
var name = match[1].replace(/\\/g, "");
|
|
|
|
if ( !isXML && Expr.attrMap[name] ) {
|
|
match[1] = Expr.attrMap[name];
|
|
}
|
|
|
|
if ( match[2] === "~=" ) {
|
|
match[4] = " " + match[4] + " ";
|
|
}
|
|
|
|
return match;
|
|
},
|
|
PSEUDO: function(match, curLoop, inplace, result, not){
|
|
if ( match[1] === "not" ) {
|
|
// If we're dealing with a complex expression, or a simple one
|
|
if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
|
|
match[3] = Sizzle(match[3], null, null, curLoop);
|
|
} else {
|
|
var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
|
|
if ( !inplace ) {
|
|
result.push.apply( result, ret );
|
|
}
|
|
return false;
|
|
}
|
|
} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
|
|
return true;
|
|
}
|
|
|
|
return match;
|
|
},
|
|
POS: function(match){
|
|
match.unshift( true );
|
|
return match;
|
|
}
|
|
},
|
|
filters: {
|
|
enabled: function(elem){
|
|
return elem.disabled === false && elem.type !== "hidden";
|
|
},
|
|
disabled: function(elem){
|
|
return elem.disabled === true;
|
|
},
|
|
checked: function(elem){
|
|
return elem.checked === true;
|
|
},
|
|
selected: function(elem){
|
|
// Accessing this property makes selected-by-default
|
|
// options in Safari work properly
|
|
elem.parentNode.selectedIndex;
|
|
return elem.selected === true;
|
|
},
|
|
parent: function(elem){
|
|
return !!elem.firstChild;
|
|
},
|
|
empty: function(elem){
|
|
return !elem.firstChild;
|
|
},
|
|
has: function(elem, i, match){
|
|
return !!Sizzle( match[3], elem ).length;
|
|
},
|
|
header: function(elem){
|
|
return /h\d/i.test( elem.nodeName );
|
|
},
|
|
text: function(elem){
|
|
return "text" === elem.type;
|
|
},
|
|
radio: function(elem){
|
|
return "radio" === elem.type;
|
|
},
|
|
checkbox: function(elem){
|
|
return "checkbox" === elem.type;
|
|
},
|
|
file: function(elem){
|
|
return "file" === elem.type;
|
|
},
|
|
password: function(elem){
|
|
return "password" === elem.type;
|
|
},
|
|
submit: function(elem){
|
|
return "submit" === elem.type;
|
|
},
|
|
image: function(elem){
|
|
return "image" === elem.type;
|
|
},
|
|
reset: function(elem){
|
|
return "reset" === elem.type;
|
|
},
|
|
button: function(elem){
|
|
return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
|
|
},
|
|
input: function(elem){
|
|
return /input|select|textarea|button/i.test(elem.nodeName);
|
|
}
|
|
},
|
|
setFilters: {
|
|
first: function(elem, i){
|
|
return i === 0;
|
|
},
|
|
last: function(elem, i, match, array){
|
|
return i === array.length - 1;
|
|
},
|
|
even: function(elem, i){
|
|
return i % 2 === 0;
|
|
},
|
|
odd: function(elem, i){
|
|
return i % 2 === 1;
|
|
},
|
|
lt: function(elem, i, match){
|
|
return i < match[3] - 0;
|
|
},
|
|
gt: function(elem, i, match){
|
|
return i > match[3] - 0;
|
|
},
|
|
nth: function(elem, i, match){
|
|
return match[3] - 0 == i;
|
|
},
|
|
eq: function(elem, i, match){
|
|
return match[3] - 0 == i;
|
|
}
|
|
},
|
|
filter: {
|
|
PSEUDO: function(elem, match, i, array){
|
|
var name = match[1], filter = Expr.filters[ name ];
|
|
|
|
if ( filter ) {
|
|
return filter( elem, i, match, array );
|
|
} else if ( name === "contains" ) {
|
|
return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
|
|
} else if ( name === "not" ) {
|
|
var not = match[3];
|
|
|
|
for ( var i = 0, l = not.length; i < l; i++ ) {
|
|
if ( not[i] === elem ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
},
|
|
CHILD: function(elem, match){
|
|
var type = match[1], node = elem;
|
|
switch (type) {
|
|
case 'only':
|
|
case 'first':
|
|
while (node = node.previousSibling) {
|
|
if ( node.nodeType === 1 ) return false;
|
|
}
|
|
if ( type == 'first') return true;
|
|
node = elem;
|
|
case 'last':
|
|
while (node = node.nextSibling) {
|
|
if ( node.nodeType === 1 ) return false;
|
|
}
|
|
return true;
|
|
case 'nth':
|
|
var first = match[2], last = match[3];
|
|
|
|
if ( first == 1 && last == 0 ) {
|
|
return true;
|
|
}
|
|
|
|
var doneName = match[0],
|
|
parent = elem.parentNode;
|
|
|
|
if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
|
|
var count = 0;
|
|
for ( node = parent.firstChild; node; node = node.nextSibling ) {
|
|
if ( node.nodeType === 1 ) {
|
|
node.nodeIndex = ++count;
|
|
}
|
|
}
|
|
parent.sizcache = doneName;
|
|
}
|
|
|
|
var diff = elem.nodeIndex - last;
|
|
if ( first == 0 ) {
|
|
return diff == 0;
|
|
} else {
|
|
return ( diff % first == 0 && diff / first >= 0 );
|
|
}
|
|
}
|
|
},
|
|
ID: function(elem, match){
|
|
return elem.nodeType === 1 && elem.getAttribute("id") === match;
|
|
},
|
|
TAG: function(elem, match){
|
|
return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
|
|
},
|
|
CLASS: function(elem, match){
|
|
return (" " + (elem.className || elem.getAttribute("class")) + " ")
|
|
.indexOf( match ) > -1;
|
|
},
|
|
ATTR: function(elem, match){
|
|
var name = match[1],
|
|
result = Expr.attrHandle[ name ] ?
|
|
Expr.attrHandle[ name ]( elem ) :
|
|
elem[ name ] != null ?
|
|
elem[ name ] :
|
|
elem.getAttribute( name ),
|
|
value = result + "",
|
|
type = match[2],
|
|
check = match[4];
|
|
|
|
return result == null ?
|
|
type === "!=" :
|
|
type === "=" ?
|
|
value === check :
|
|
type === "*=" ?
|
|
value.indexOf(check) >= 0 :
|
|
type === "~=" ?
|
|
(" " + value + " ").indexOf(check) >= 0 :
|
|
!check ?
|
|
value && result !== false :
|
|
type === "!=" ?
|
|
value != check :
|
|
type === "^=" ?
|
|
value.indexOf(check) === 0 :
|
|
type === "$=" ?
|
|
value.substr(value.length - check.length) === check :
|
|
type === "|=" ?
|
|
value === check || value.substr(0, check.length + 1) === check + "-" :
|
|
false;
|
|
},
|
|
POS: function(elem, match, i, array){
|
|
var name = match[2], filter = Expr.setFilters[ name ];
|
|
|
|
if ( filter ) {
|
|
return filter( elem, i, match, array );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var origPOS = Expr.match.POS;
|
|
|
|
for ( var type in Expr.match ) {
|
|
Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
|
|
}
|
|
|
|
var makeArray = function(array, results) {
|
|
array = Array.prototype.slice.call( array );
|
|
|
|
if ( results ) {
|
|
results.push.apply( results, array );
|
|
return results;
|
|
}
|
|
|
|
return array;
|
|
};
|
|
|
|
// Perform a simple check to determine if the browser is capable of
|
|
// converting a NodeList to an array using builtin methods.
|
|
try {
|
|
Array.prototype.slice.call( document.documentElement.childNodes );
|
|
|
|
// Provide a fallback method if it does not work
|
|
} catch(e){
|
|
makeArray = function(array, results) {
|
|
var ret = results || [];
|
|
|
|
if ( toString.call(array) === "[object Array]" ) {
|
|
Array.prototype.push.apply( ret, array );
|
|
} else {
|
|
if ( typeof array.length === "number" ) {
|
|
for ( var i = 0, l = array.length; i < l; i++ ) {
|
|
ret.push( array[i] );
|
|
}
|
|
} else {
|
|
for ( var i = 0; array[i]; i++ ) {
|
|
ret.push( array[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
}
|
|
|
|
var sortOrder;
|
|
|
|
if ( document.documentElement.compareDocumentPosition ) {
|
|
sortOrder = function( a, b ) {
|
|
var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
|
|
if ( ret === 0 ) {
|
|
hasDuplicate = true;
|
|
}
|
|
return ret;
|
|
};
|
|
} else if ( "sourceIndex" in document.documentElement ) {
|
|
sortOrder = function( a, b ) {
|
|
var ret = a.sourceIndex - b.sourceIndex;
|
|
if ( ret === 0 ) {
|
|
hasDuplicate = true;
|
|
}
|
|
return ret;
|
|
};
|
|
} else if ( document.createRange ) {
|
|
sortOrder = function( a, b ) {
|
|
var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
|
|
aRange.setStart(a, 0);
|
|
aRange.setEnd(a, 0);
|
|
bRange.setStart(b, 0);
|
|
bRange.setEnd(b, 0);
|
|
var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
|
|
if ( ret === 0 ) {
|
|
hasDuplicate = true;
|
|
}
|
|
return ret;
|
|
};
|
|
}
|
|
|
|
// Check to see if the browser returns elements by name when
|
|
// querying by getElementById (and provide a workaround)
|
|
(function(){
|
|
// We're going to inject a fake input element with a specified name
|
|
var form = document.createElement("div"),
|
|
id = "script" + (new Date).getTime();
|
|
form.innerHTML = "<a name='" + id + "'/>";
|
|
|
|
// Inject it into the root element, check its status, and remove it quickly
|
|
var root = document.documentElement;
|
|
root.insertBefore( form, root.firstChild );
|
|
|
|
// The workaround has to do additional checks after a getElementById
|
|
// Which slows things down for other browsers (hence the branching)
|
|
if ( !!document.getElementById( id ) ) {
|
|
Expr.find.ID = function(match, context, isXML){
|
|
if ( typeof context.getElementById !== "undefined" && !isXML ) {
|
|
var m = context.getElementById(match[1]);
|
|
return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
|
|
}
|
|
};
|
|
|
|
Expr.filter.ID = function(elem, match){
|
|
var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
|
|
return elem.nodeType === 1 && node && node.nodeValue === match;
|
|
};
|
|
}
|
|
|
|
root.removeChild( form );
|
|
})();
|
|
|
|
(function(){
|
|
// Check to see if the browser returns only elements
|
|
// when doing getElementsByTagName("*")
|
|
|
|
// Create a fake element
|
|
var div = document.createElement("div");
|
|
div.appendChild( document.createComment("") );
|
|
|
|
// Make sure no comments are found
|
|
if ( div.getElementsByTagName("*").length > 0 ) {
|
|
Expr.find.TAG = function(match, context){
|
|
var results = context.getElementsByTagName(match[1]);
|
|
|
|
// Filter out possible comments
|
|
if ( match[1] === "*" ) {
|
|
var tmp = [];
|
|
|
|
for ( var i = 0; results[i]; i++ ) {
|
|
if ( results[i].nodeType === 1 ) {
|
|
tmp.push( results[i] );
|
|
}
|
|
}
|
|
|
|
results = tmp;
|
|
}
|
|
|
|
return results;
|
|
};
|
|
}
|
|
|
|
// Check to see if an attribute returns normalized href attributes
|
|
div.innerHTML = "<a href='#'></a>";
|
|
if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
|
|
div.firstChild.getAttribute("href") !== "#" ) {
|
|
Expr.attrHandle.href = function(elem){
|
|
return elem.getAttribute("href", 2);
|
|
};
|
|
}
|
|
})();
|
|
|
|
if ( document.querySelectorAll ) (function(){
|
|
var oldSizzle = Sizzle, div = document.createElement("div");
|
|
div.innerHTML = "<p class='TEST'></p>";
|
|
|
|
// Safari can't handle uppercase or unicode characters when
|
|
// in quirks mode.
|
|
if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
|
|
return;
|
|
}
|
|
|
|
Sizzle = function(query, context, extra, seed){
|
|
context = context || document;
|
|
|
|
// Only use querySelectorAll on non-XML documents
|
|
// (ID selectors don't work in non-HTML documents)
|
|
if ( !seed && context.nodeType === 9 && !isXML(context) ) {
|
|
try {
|
|
return makeArray( context.querySelectorAll(query), extra );
|
|
} catch(e){}
|
|
}
|
|
|
|
return oldSizzle(query, context, extra, seed);
|
|
};
|
|
|
|
for ( var prop in oldSizzle ) {
|
|
Sizzle[ prop ] = oldSizzle[ prop ];
|
|
}
|
|
})();
|
|
|
|
if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
|
|
var div = document.createElement("div");
|
|
div.innerHTML = "<div class='test e'></div><div class='test'></div>";
|
|
|
|
// Opera can't find a second classname (in 9.6)
|
|
if ( div.getElementsByClassName("e").length === 0 )
|
|
return;
|
|
|
|
// Safari caches class attributes, doesn't catch changes (in 3.2)
|
|
div.lastChild.className = "e";
|
|
|
|
if ( div.getElementsByClassName("e").length === 1 )
|
|
return;
|
|
|
|
Expr.order.splice(1, 0, "CLASS");
|
|
Expr.find.CLASS = function(match, context, isXML) {
|
|
if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
|
|
return context.getElementsByClassName(match[1]);
|
|
}
|
|
};
|
|
})();
|
|
|
|
function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
|
|
var sibDir = dir == "previousSibling" && !isXML;
|
|
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
|
|
var elem = checkSet[i];
|
|
if ( elem ) {
|
|
if ( sibDir && elem.nodeType === 1 ){
|
|
elem.sizcache = doneName;
|
|
elem.sizset = i;
|
|
}
|
|
elem = elem[dir];
|
|
var match = false;
|
|
|
|
while ( elem ) {
|
|
if ( elem.sizcache === doneName ) {
|
|
match = checkSet[elem.sizset];
|
|
break;
|
|
}
|
|
|
|
if ( elem.nodeType === 1 && !isXML ){
|
|
elem.sizcache = doneName;
|
|
elem.sizset = i;
|
|
}
|
|
|
|
if ( elem.nodeName === cur ) {
|
|
match = elem;
|
|
break;
|
|
}
|
|
|
|
elem = elem[dir];
|
|
}
|
|
|
|
checkSet[i] = match;
|
|
}
|
|
}
|
|
}
|
|
|
|
function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
|
|
var sibDir = dir == "previousSibling" && !isXML;
|
|
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
|
|
var elem = checkSet[i];
|
|
if ( elem ) {
|
|
if ( sibDir && elem.nodeType === 1 ) {
|
|
elem.sizcache = doneName;
|
|
elem.sizset = i;
|
|
}
|
|
elem = elem[dir];
|
|
var match = false;
|
|
|
|
while ( elem ) {
|
|
if ( elem.sizcache === doneName ) {
|
|
match = checkSet[elem.sizset];
|
|
break;
|
|
}
|
|
|
|
if ( elem.nodeType === 1 ) {
|
|
if ( !isXML ) {
|
|
elem.sizcache = doneName;
|
|
elem.sizset = i;
|
|
}
|
|
if ( typeof cur !== "string" ) {
|
|
if ( elem === cur ) {
|
|
match = true;
|
|
break;
|
|
}
|
|
|
|
} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
|
|
match = elem;
|
|
break;
|
|
}
|
|
}
|
|
|
|
elem = elem[dir];
|
|
}
|
|
|
|
checkSet[i] = match;
|
|
}
|
|
}
|
|
}
|
|
|
|
var contains = document.compareDocumentPosition ? function(a, b){
|
|
return a.compareDocumentPosition(b) & 16;
|
|
} : function(a, b){
|
|
return a !== b && (a.contains ? a.contains(b) : true);
|
|
};
|
|
|
|
var isXML = function(elem){
|
|
return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
|
|
!!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
|
|
};
|
|
|
|
var posProcess = function(selector, context){
|
|
var tmpSet = [], later = "", match,
|
|
root = context.nodeType ? [context] : context;
|
|
|
|
// Position selectors must be done after the filter
|
|
// And so must :not(positional) so we move all PSEUDOs to the end
|
|
while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
|
|
later += match[0];
|
|
selector = selector.replace( Expr.match.PSEUDO, "" );
|
|
}
|
|
|
|
selector = Expr.relative[selector] ? selector + "*" : selector;
|
|
|
|
for ( var i = 0, l = root.length; i < l; i++ ) {
|
|
Sizzle( selector, root[i], tmpSet );
|
|
}
|
|
|
|
return Sizzle.filter( later, tmpSet );
|
|
};
|
|
|
|
// EXPOSE
|
|
|
|
window.tinymce.dom.Sizzle = Sizzle;
|
|
|
|
})();
|
|
|
|
(function(tinymce) {
|
|
// Shorten names
|
|
var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
|
|
|
|
tinymce.create('tinymce.dom.EventUtils', {
|
|
EventUtils : function() {
|
|
this.inits = [];
|
|
this.events = [];
|
|
},
|
|
|
|
add : function(o, n, f, s) {
|
|
var cb, t = this, el = t.events, r;
|
|
|
|
if (n instanceof Array) {
|
|
r = [];
|
|
|
|
each(n, function(n) {
|
|
r.push(t.add(o, n, f, s));
|
|
});
|
|
|
|
return r;
|
|
}
|
|
|
|
// Handle array
|
|
if (o && o.hasOwnProperty && o instanceof Array) {
|
|
r = [];
|
|
|
|
each(o, function(o) {
|
|
o = DOM.get(o);
|
|
r.push(t.add(o, n, f, s));
|
|
});
|
|
|
|
return r;
|
|
}
|
|
|
|
o = DOM.get(o);
|
|
|
|
if (!o)
|
|
return;
|
|
|
|
// Setup event callback
|
|
cb = function(e) {
|
|
// Is all events disabled
|
|
if (t.disabled)
|
|
return;
|
|
|
|
e = e || window.event;
|
|
|
|
// Patch in target, preventDefault and stopPropagation in IE it's W3C valid
|
|
if (e && isIE) {
|
|
if (!e.target)
|
|
e.target = e.srcElement;
|
|
|
|
// Patch in preventDefault, stopPropagation methods for W3C compatibility
|
|
tinymce.extend(e, t._stoppers);
|
|
}
|
|
|
|
if (!s)
|
|
return f(e);
|
|
|
|
return f.call(s, e);
|
|
};
|
|
|
|
if (n == 'unload') {
|
|
tinymce.unloads.unshift({func : cb});
|
|
return cb;
|
|
}
|
|
|
|
if (n == 'init') {
|
|
if (t.domLoaded)
|
|
cb();
|
|
else
|
|
t.inits.push(cb);
|
|
|
|
return cb;
|
|
}
|
|
|
|
// Store away listener reference
|
|
el.push({
|
|
obj : o,
|
|
name : n,
|
|
func : f,
|
|
cfunc : cb,
|
|
scope : s
|
|
});
|
|
|
|
t._add(o, n, cb);
|
|
|
|
return f;
|
|
},
|
|
|
|
remove : function(o, n, f) {
|
|
var t = this, a = t.events, s = false, r;
|
|
|
|
// Handle array
|
|
if (o && o.hasOwnProperty && o instanceof Array) {
|
|
r = [];
|
|
|
|
each(o, function(o) {
|
|
o = DOM.get(o);
|
|
r.push(t.remove(o, n, f));
|
|
});
|
|
|
|
return r;
|
|
}
|
|
|
|
o = DOM.get(o);
|
|
|
|
each(a, function(e, i) {
|
|
if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
|
|
a.splice(i, 1);
|
|
t._remove(o, n, e.cfunc);
|
|
s = true;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return s;
|
|
},
|
|
|
|
clear : function(o) {
|
|
var t = this, a = t.events, i, e;
|
|
|
|
if (o) {
|
|
o = DOM.get(o);
|
|
|
|
for (i = a.length - 1; i >= 0; i--) {
|
|
e = a[i];
|
|
|
|
if (e.obj === o) {
|
|
t._remove(e.obj, e.name, e.cfunc);
|
|
e.obj = e.cfunc = null;
|
|
a.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
cancel : function(e) {
|
|
if (!e)
|
|
return false;
|
|
|
|
this.stop(e);
|
|
|
|
return this.prevent(e);
|
|
},
|
|
|
|
stop : function(e) {
|
|
if (e.stopPropagation)
|
|
e.stopPropagation();
|
|
else
|
|
e.cancelBubble = true;
|
|
|
|
return false;
|
|
},
|
|
|
|
prevent : function(e) {
|
|
if (e.preventDefault)
|
|
e.preventDefault();
|
|
else
|
|
e.returnValue = false;
|
|
|
|
return false;
|
|
},
|
|
|
|
destroy : function() {
|
|
var t = this;
|
|
|
|
each(t.events, function(e, i) {
|
|
t._remove(e.obj, e.name, e.cfunc);
|
|
e.obj = e.cfunc = null;
|
|
});
|
|
|
|
t.events = [];
|
|
t = null;
|
|
},
|
|
|
|
_add : function(o, n, f) {
|
|
if (o.attachEvent)
|
|
o.attachEvent('on' + n, f);
|
|
else if (o.addEventListener)
|
|
o.addEventListener(n, f, false);
|
|
else
|
|
o['on' + n] = f;
|
|
},
|
|
|
|
_remove : function(o, n, f) {
|
|
if (o) {
|
|
try {
|
|
if (o.detachEvent)
|
|
o.detachEvent('on' + n, f);
|
|
else if (o.removeEventListener)
|
|
o.removeEventListener(n, f, false);
|
|
else
|
|
o['on' + n] = null;
|
|
} catch (ex) {
|
|
// Might fail with permission denined on IE so we just ignore that
|
|
}
|
|
}
|
|
},
|
|
|
|
_pageInit : function(win) {
|
|
var t = this;
|
|
|
|
// Keep it from running more than once
|
|
if (t.domLoaded)
|
|
return;
|
|
|
|
t.domLoaded = true;
|
|
|
|
each(t.inits, function(c) {
|
|
c();
|
|
});
|
|
|
|
t.inits = [];
|
|
},
|
|
|
|
_wait : function(win) {
|
|
var t = this, doc = win.document;
|
|
|
|
// No need since the document is already loaded
|
|
if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
|
|
t.domLoaded = 1;
|
|
return;
|
|
}
|
|
|
|
// Use IE method
|
|
if (doc.attachEvent) {
|
|
doc.attachEvent("onreadystatechange", function() {
|
|
if (doc.readyState === "complete") {
|
|
doc.detachEvent("onreadystatechange", arguments.callee);
|
|
t._pageInit(win);
|
|
}
|
|
});
|
|
|
|
if (doc.documentElement.doScroll && win == win.top) {
|
|
(function() {
|
|
if (t.domLoaded)
|
|
return;
|
|
|
|
try {
|
|
// If IE is used, use the trick by Diego Perini
|
|
// http://javascript.nwbox.com/IEContentLoaded/
|
|
doc.documentElement.doScroll("left");
|
|
} catch (ex) {
|
|
setTimeout(arguments.callee, 0);
|
|
return;
|
|
}
|
|
|
|
t._pageInit(win);
|
|
})();
|
|
}
|
|
} else if (doc.addEventListener) {
|
|
t._add(win, 'DOMContentLoaded', function() {
|
|
t._pageInit(win);
|
|
});
|
|
}
|
|
|
|
t._add(win, 'load', function() {
|
|
t._pageInit(win);
|
|
});
|
|
},
|
|
|
|
_stoppers : {
|
|
preventDefault : function() {
|
|
this.returnValue = false;
|
|
},
|
|
|
|
stopPropagation : function() {
|
|
this.cancelBubble = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
|
|
|
|
// Dispatch DOM content loaded event for IE and Safari
|
|
Event._wait(window);
|
|
|
|
tinymce.addUnload(function() {
|
|
Event.destroy();
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
var each = tinymce.each;
|
|
|
|
tinymce.create('tinymce.dom.Element', {
|
|
Element : function(id, s) {
|
|
var t = this, dom, el;
|
|
|
|
s = s || {};
|
|
t.id = id;
|
|
t.dom = dom = s.dom || tinymce.DOM;
|
|
t.settings = s;
|
|
|
|
// Only IE leaks DOM references, this is a lot faster
|
|
if (!tinymce.isIE)
|
|
el = t.dom.get(t.id);
|
|
|
|
each([
|
|
'getPos',
|
|
'getRect',
|
|
'getParent',
|
|
'add',
|
|
'setStyle',
|
|
'getStyle',
|
|
'setStyles',
|
|
'setAttrib',
|
|
'setAttribs',
|
|
'getAttrib',
|
|
'addClass',
|
|
'removeClass',
|
|
'hasClass',
|
|
'getOuterHTML',
|
|
'setOuterHTML',
|
|
'remove',
|
|
'show',
|
|
'hide',
|
|
'isHidden',
|
|
'setHTML',
|
|
'get'
|
|
], function(k) {
|
|
t[k] = function() {
|
|
var a = [id], i;
|
|
|
|
for (i = 0; i < arguments.length; i++)
|
|
a.push(arguments[i]);
|
|
|
|
a = dom[k].apply(dom, a);
|
|
t.update(k);
|
|
|
|
return a;
|
|
};
|
|
});
|
|
},
|
|
|
|
on : function(n, f, s) {
|
|
return tinymce.dom.Event.add(this.id, n, f, s);
|
|
},
|
|
|
|
getXY : function() {
|
|
return {
|
|
x : parseInt(this.getStyle('left')),
|
|
y : parseInt(this.getStyle('top'))
|
|
};
|
|
},
|
|
|
|
getSize : function() {
|
|
var n = this.dom.get(this.id);
|
|
|
|
return {
|
|
w : parseInt(this.getStyle('width') || n.clientWidth),
|
|
h : parseInt(this.getStyle('height') || n.clientHeight)
|
|
};
|
|
},
|
|
|
|
moveTo : function(x, y) {
|
|
this.setStyles({left : x, top : y});
|
|
},
|
|
|
|
moveBy : function(x, y) {
|
|
var p = this.getXY();
|
|
|
|
this.moveTo(p.x + x, p.y + y);
|
|
},
|
|
|
|
resizeTo : function(w, h) {
|
|
this.setStyles({width : w, height : h});
|
|
},
|
|
|
|
resizeBy : function(w, h) {
|
|
var s = this.getSize();
|
|
|
|
this.resizeTo(s.w + w, s.h + h);
|
|
},
|
|
|
|
update : function(k) {
|
|
var t = this, b, dom = t.dom;
|
|
|
|
if (tinymce.isIE6 && t.settings.blocker) {
|
|
k = k || '';
|
|
|
|
// Ignore getters
|
|
if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
|
|
return;
|
|
|
|
// Remove blocker on remove
|
|
if (k == 'remove') {
|
|
dom.remove(t.blocker);
|
|
return;
|
|
}
|
|
|
|
if (!t.blocker) {
|
|
t.blocker = dom.uniqueId();
|
|
b = dom.add(t.settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
|
|
dom.setStyle(b, 'opacity', 0);
|
|
} else
|
|
b = dom.get(t.blocker);
|
|
|
|
dom.setStyle(b, 'left', t.getStyle('left', 1));
|
|
dom.setStyle(b, 'top', t.getStyle('top', 1));
|
|
dom.setStyle(b, 'width', t.getStyle('width', 1));
|
|
dom.setStyle(b, 'height', t.getStyle('height', 1));
|
|
dom.setStyle(b, 'display', t.getStyle('display', 1));
|
|
dom.setStyle(b, 'zIndex', parseInt(t.getStyle('zIndex', 1) || 0) - 1);
|
|
}
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
function trimNl(s) {
|
|
return s.replace(/[\n\r]+/g, '');
|
|
};
|
|
|
|
// Shorten names
|
|
var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
|
|
|
|
tinymce.create('tinymce.dom.Selection', {
|
|
Selection : function(dom, win, serializer) {
|
|
var t = this;
|
|
|
|
t.dom = dom;
|
|
t.win = win;
|
|
t.serializer = serializer;
|
|
|
|
// Add events
|
|
each([
|
|
'onBeforeSetContent',
|
|
'onBeforeGetContent',
|
|
'onSetContent',
|
|
'onGetContent'
|
|
], function(e) {
|
|
t[e] = new tinymce.util.Dispatcher(t);
|
|
});
|
|
|
|
// No W3C Range support
|
|
if (!t.win.getSelection)
|
|
t.tridentSel = new tinymce.dom.TridentSelection(t);
|
|
|
|
// Prevent leaks
|
|
tinymce.addUnload(t.destroy, t);
|
|
},
|
|
|
|
getContent : function(s) {
|
|
var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
|
|
|
|
s = s || {};
|
|
wb = wa = '';
|
|
s.get = true;
|
|
s.format = s.format || 'html';
|
|
t.onBeforeGetContent.dispatch(t, s);
|
|
|
|
if (s.format == 'text')
|
|
return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
|
|
|
|
if (r.cloneContents) {
|
|
n = r.cloneContents();
|
|
|
|
if (n)
|
|
e.appendChild(n);
|
|
} else if (is(r.item) || is(r.htmlText))
|
|
e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
|
|
else
|
|
e.innerHTML = r.toString();
|
|
|
|
// Keep whitespace before and after
|
|
if (/^\s/.test(e.innerHTML))
|
|
wb = ' ';
|
|
|
|
if (/\s+$/.test(e.innerHTML))
|
|
wa = ' ';
|
|
|
|
s.getInner = true;
|
|
|
|
s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
|
|
t.onGetContent.dispatch(t, s);
|
|
|
|
return s.content;
|
|
},
|
|
|
|
setContent : function(h, s) {
|
|
var t = this, r = t.getRng(), c, d = t.win.document;
|
|
|
|
s = s || {format : 'html'};
|
|
s.set = true;
|
|
h = s.content = t.dom.processHTML(h);
|
|
|
|
// Dispatch before set content event
|
|
t.onBeforeSetContent.dispatch(t, s);
|
|
h = s.content;
|
|
|
|
if (r.insertNode) {
|
|
// Make caret marker since insertNode places the caret in the beginning of text after insert
|
|
h += '<span id="__caret">_</span>';
|
|
|
|
// Delete and insert new node
|
|
r.deleteContents();
|
|
r.insertNode(t.getRng().createContextualFragment(h));
|
|
|
|
// Move to caret marker
|
|
c = t.dom.get('__caret');
|
|
|
|
// Make sure we wrap it compleatly, Opera fails with a simple select call
|
|
r = d.createRange();
|
|
r.setStartBefore(c);
|
|
r.setEndAfter(c);
|
|
t.setRng(r);
|
|
|
|
// Delete the marker, and hopefully the caret gets placed in the right location
|
|
// Removed this since it seems to remove in FF and simply deleting it
|
|
// doesn't seem to affect the caret position in any browser
|
|
//d.execCommand('Delete', false, null);
|
|
|
|
// Remove the caret position
|
|
t.dom.remove('__caret');
|
|
} else {
|
|
if (r.item) {
|
|
// Delete content and get caret text selection
|
|
d.execCommand('Delete', false, null);
|
|
r = t.getRng();
|
|
}
|
|
|
|
r.pasteHTML(h);
|
|
}
|
|
|
|
// Dispatch set content event
|
|
t.onSetContent.dispatch(t, s);
|
|
},
|
|
|
|
getStart : function() {
|
|
var t = this, r = t.getRng(), e;
|
|
|
|
if (isIE) {
|
|
if (r.item)
|
|
return r.item(0);
|
|
|
|
r = r.duplicate();
|
|
r.collapse(1);
|
|
e = r.parentElement();
|
|
|
|
if (e && e.nodeName == 'BODY')
|
|
return e.firstChild;
|
|
|
|
return e;
|
|
} else {
|
|
e = r.startContainer;
|
|
|
|
if (e.nodeName == 'BODY')
|
|
return e.firstChild;
|
|
|
|
return t.dom.getParent(e, '*');
|
|
}
|
|
},
|
|
|
|
getEnd : function() {
|
|
var t = this, r = t.getRng(), e;
|
|
|
|
if (isIE) {
|
|
if (r.item)
|
|
return r.item(0);
|
|
|
|
r = r.duplicate();
|
|
r.collapse(0);
|
|
e = r.parentElement();
|
|
|
|
if (e && e.nodeName == 'BODY')
|
|
return e.lastChild;
|
|
|
|
return e;
|
|
} else {
|
|
e = r.endContainer;
|
|
|
|
if (e.nodeName == 'BODY')
|
|
return e.lastChild;
|
|
|
|
return t.dom.getParent(e, '*');
|
|
}
|
|
},
|
|
|
|
getBookmark : function(si) {
|
|
var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv;
|
|
sx = vp.x;
|
|
sy = vp.y;
|
|
|
|
// Simple bookmark fast but not as persistent
|
|
if (si)
|
|
return {rng : r, scrollX : sx, scrollY : sy};
|
|
|
|
// Handle IE
|
|
if (isIE) {
|
|
// Control selection
|
|
if (r.item) {
|
|
e = r.item(0);
|
|
|
|
each(t.dom.select(e.nodeName), function(n, i) {
|
|
if (e == n) {
|
|
sp = i;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return {
|
|
tag : e.nodeName,
|
|
index : sp,
|
|
scrollX : sx,
|
|
scrollY : sy
|
|
};
|
|
}
|
|
|
|
// Text selection
|
|
tr = t.dom.doc.body.createTextRange();
|
|
tr.moveToElementText(ro);
|
|
tr.collapse(true);
|
|
bp = Math.abs(tr.move('character', c));
|
|
|
|
tr = r.duplicate();
|
|
tr.collapse(true);
|
|
sp = Math.abs(tr.move('character', c));
|
|
|
|
tr = r.duplicate();
|
|
tr.collapse(false);
|
|
le = Math.abs(tr.move('character', c)) - sp;
|
|
|
|
return {
|
|
start : sp - bp,
|
|
length : le,
|
|
scrollX : sx,
|
|
scrollY : sy
|
|
};
|
|
}
|
|
|
|
// Handle W3C
|
|
e = t.getNode();
|
|
s = t.getSel();
|
|
|
|
if (!s)
|
|
return null;
|
|
|
|
// Image selection
|
|
if (e && e.nodeName == 'IMG') {
|
|
return {
|
|
scrollX : sx,
|
|
scrollY : sy
|
|
};
|
|
}
|
|
|
|
// Text selection
|
|
|
|
function getPos(r, sn, en) {
|
|
var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {};
|
|
|
|
while ((n = w.nextNode()) != null) {
|
|
if (n == sn)
|
|
d.start = p;
|
|
|
|
if (n == en) {
|
|
d.end = p;
|
|
return d;
|
|
}
|
|
|
|
p += trimNl(n.nodeValue || '').length;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
// Caret or selection
|
|
if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) {
|
|
e = getPos(ro, s.anchorNode, s.focusNode);
|
|
|
|
if (!e)
|
|
return {scrollX : sx, scrollY : sy};
|
|
|
|
// Count whitespace before
|
|
trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;});
|
|
|
|
return {
|
|
start : Math.max(e.start + s.anchorOffset - wb, 0),
|
|
end : Math.max(e.end + s.focusOffset - wb, 0),
|
|
scrollX : sx,
|
|
scrollY : sy,
|
|
beg : s.anchorOffset - wb == 0
|
|
};
|
|
} else {
|
|
e = getPos(ro, r.startContainer, r.endContainer);
|
|
|
|
// Count whitespace before start and end container
|
|
//(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;});
|
|
//(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;});
|
|
|
|
if (!e)
|
|
return {scrollX : sx, scrollY : sy};
|
|
|
|
return {
|
|
start : Math.max(e.start + r.startOffset - wb, 0),
|
|
end : Math.max(e.end + r.endOffset - wa, 0),
|
|
scrollX : sx,
|
|
scrollY : sy,
|
|
beg : r.startOffset - wb == 0
|
|
};
|
|
}
|
|
},
|
|
|
|
moveToBookmark : function(b) {
|
|
var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv;
|
|
|
|
function getPos(r, sp, ep) {
|
|
var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb;
|
|
|
|
while ((n = w.nextNode()) != null) {
|
|
wa = wb = 0;
|
|
|
|
nv = n.nodeValue || '';
|
|
//nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;});
|
|
//nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;});
|
|
|
|
nvl = trimNl(nv).length;
|
|
p += nvl;
|
|
|
|
if (p >= sp && !d.startNode) {
|
|
o = sp - (p - nvl);
|
|
|
|
// Fix for odd quirk in FF
|
|
if (b.beg && o >= nvl)
|
|
continue;
|
|
|
|
d.startNode = n;
|
|
d.startOffset = o + wb;
|
|
}
|
|
|
|
if (p >= ep) {
|
|
d.endNode = n;
|
|
d.endOffset = ep - (p - nvl) + wb;
|
|
return d;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
if (!b)
|
|
return false;
|
|
|
|
t.win.scrollTo(b.scrollX, b.scrollY);
|
|
|
|
// Handle explorer
|
|
if (isIE) {
|
|
t.tridentSel.destroy();
|
|
|
|
// Handle simple
|
|
if (r = b.rng) {
|
|
try {
|
|
r.select();
|
|
} catch (ex) {
|
|
// Ignore
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
t.win.focus();
|
|
|
|
// Handle control bookmark
|
|
if (b.tag) {
|
|
r = ro.createControlRange();
|
|
|
|
each(t.dom.select(b.tag), function(n, i) {
|
|
if (i == b.index)
|
|
r.addElement(n);
|
|
});
|
|
} else {
|
|
// Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs
|
|
try {
|
|
// Incorrect bookmark
|
|
if (b.start < 0)
|
|
return true;
|
|
|
|
r = s.createRange();
|
|
r.moveToElementText(ro);
|
|
r.collapse(true);
|
|
r.moveStart('character', b.start);
|
|
r.moveEnd('character', b.length);
|
|
} catch (ex2) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
try {
|
|
r.select();
|
|
} catch (ex) {
|
|
// Needed for some odd IE bug #1843306
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Handle W3C
|
|
if (!s)
|
|
return false;
|
|
|
|
// Handle simple
|
|
if (b.rng) {
|
|
s.removeAllRanges();
|
|
s.addRange(b.rng);
|
|
} else {
|
|
if (is(b.start) && is(b.end)) {
|
|
try {
|
|
sd = getPos(ro, b.start, b.end);
|
|
|
|
if (sd) {
|
|
r = t.dom.doc.createRange();
|
|
r.setStart(sd.startNode, sd.startOffset);
|
|
r.setEnd(sd.endNode, sd.endOffset);
|
|
s.removeAllRanges();
|
|
s.addRange(r);
|
|
}
|
|
|
|
if (!tinymce.isOpera)
|
|
t.win.focus();
|
|
} catch (ex) {
|
|
// Ignore
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
select : function(n, c) {
|
|
var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document;
|
|
|
|
function find(n, start) {
|
|
var walker, o;
|
|
|
|
if (n) {
|
|
walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
|
|
|
|
// Find first/last non empty text node
|
|
while (n = walker.nextNode()) {
|
|
o = n;
|
|
|
|
if (tinymce.trim(n.nodeValue).length != 0) {
|
|
if (start)
|
|
return n;
|
|
else
|
|
o = n;
|
|
}
|
|
}
|
|
}
|
|
|
|
return o;
|
|
};
|
|
|
|
if (isIE) {
|
|
try {
|
|
b = d.body;
|
|
|
|
if (/^(IMG|TABLE)$/.test(n.nodeName)) {
|
|
r = b.createControlRange();
|
|
r.addElement(n);
|
|
} else {
|
|
r = b.createTextRange();
|
|
r.moveToElementText(n);
|
|
}
|
|
|
|
r.select();
|
|
} catch (ex) {
|
|
// Throws illigal agrument in IE some times
|
|
}
|
|
} else {
|
|
if (c) {
|
|
fn = find(n, 1) || t.dom.select('br:first', n)[0];
|
|
ln = find(n, 0) || t.dom.select('br:last', n)[0];
|
|
|
|
if (fn && ln) {
|
|
r = d.createRange();
|
|
|
|
if (fn.nodeName == 'BR')
|
|
r.setStartBefore(fn);
|
|
else
|
|
r.setStart(fn, 0);
|
|
|
|
if (ln.nodeName == 'BR')
|
|
r.setEndBefore(ln);
|
|
else
|
|
r.setEnd(ln, ln.nodeValue.length);
|
|
} else
|
|
r.selectNode(n);
|
|
} else
|
|
r.selectNode(n);
|
|
|
|
t.setRng(r);
|
|
}
|
|
|
|
return n;
|
|
},
|
|
|
|
isCollapsed : function() {
|
|
var t = this, r = t.getRng(), s = t.getSel();
|
|
|
|
if (!r || r.item)
|
|
return false;
|
|
|
|
return !s || r.boundingWidth == 0 || r.collapsed;
|
|
},
|
|
|
|
collapse : function(b) {
|
|
var t = this, r = t.getRng(), n;
|
|
|
|
// Control range on IE
|
|
if (r.item) {
|
|
n = r.item(0);
|
|
r = this.win.document.body.createTextRange();
|
|
r.moveToElementText(n);
|
|
}
|
|
|
|
r.collapse(!!b);
|
|
t.setRng(r);
|
|
},
|
|
|
|
getSel : function() {
|
|
var t = this, w = this.win;
|
|
|
|
return w.getSelection ? w.getSelection() : w.document.selection;
|
|
},
|
|
|
|
getRng : function(w3c) {
|
|
var t = this, s, r;
|
|
|
|
// Found tridentSel object then we need to use that one
|
|
if (w3c && t.tridentSel)
|
|
return t.tridentSel.getRangeAt(0);
|
|
|
|
try {
|
|
if (s = t.getSel())
|
|
r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
|
|
} catch (ex) {
|
|
// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
|
|
}
|
|
|
|
// No range found then create an empty one
|
|
// This can occur when the editor is placed in a hidden container element on Gecko
|
|
// Or on IE when there was an exception
|
|
if (!r)
|
|
r = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange();
|
|
|
|
return r;
|
|
},
|
|
|
|
setRng : function(r) {
|
|
var s, t = this;
|
|
|
|
if (!t.tridentSel) {
|
|
s = t.getSel();
|
|
|
|
if (s) {
|
|
s.removeAllRanges();
|
|
s.addRange(r);
|
|
}
|
|
} else {
|
|
// Is W3C Range
|
|
if (r.cloneRange) {
|
|
t.tridentSel.addRange(r);
|
|
return;
|
|
}
|
|
|
|
// Is IE specific range
|
|
try {
|
|
r.select();
|
|
} catch (ex) {
|
|
// Needed for some odd IE bug #1843306
|
|
}
|
|
}
|
|
},
|
|
|
|
setNode : function(n) {
|
|
var t = this;
|
|
|
|
t.setContent(t.dom.getOuterHTML(n));
|
|
|
|
return n;
|
|
},
|
|
|
|
getNode : function() {
|
|
var t = this, r = t.getRng(), s = t.getSel(), e;
|
|
|
|
if (!isIE) {
|
|
// Range maybe lost after the editor is made visible again
|
|
if (!r)
|
|
return t.dom.getRoot();
|
|
|
|
e = r.commonAncestorContainer;
|
|
|
|
// Handle selection a image or other control like element such as anchors
|
|
if (!r.collapsed) {
|
|
// If the anchor node is a element instead of a text node then return this element
|
|
if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1)
|
|
return s.anchorNode.childNodes[s.anchorOffset];
|
|
|
|
if (r.startContainer == r.endContainer) {
|
|
if (r.startOffset - r.endOffset < 2) {
|
|
if (r.startContainer.hasChildNodes())
|
|
e = r.startContainer.childNodes[r.startOffset];
|
|
}
|
|
}
|
|
}
|
|
|
|
return t.dom.getParent(e, '*');
|
|
}
|
|
|
|
return r.item ? r.item(0) : r.parentElement();
|
|
},
|
|
|
|
getSelectedBlocks : function(st, en) {
|
|
var t = this, dom = t.dom, sb, eb, n, bl = [];
|
|
|
|
sb = dom.getParent(st || t.getStart(), dom.isBlock);
|
|
eb = dom.getParent(en || t.getEnd(), dom.isBlock);
|
|
|
|
if (sb)
|
|
bl.push(sb);
|
|
|
|
if (sb && eb && sb != eb) {
|
|
n = sb;
|
|
|
|
while ((n = n.nextSibling) && n != eb) {
|
|
if (dom.isBlock(n))
|
|
bl.push(n);
|
|
}
|
|
}
|
|
|
|
if (eb && sb != eb)
|
|
bl.push(eb);
|
|
|
|
return bl;
|
|
},
|
|
|
|
destroy : function(s) {
|
|
var t = this;
|
|
|
|
t.win = null;
|
|
|
|
if (t.tridentSel)
|
|
t.tridentSel.destroy();
|
|
|
|
// Manual destroy then remove unload handler
|
|
if (!s)
|
|
tinymce.removeUnload(t.destroy);
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
tinymce.create('tinymce.dom.XMLWriter', {
|
|
node : null,
|
|
|
|
XMLWriter : function(s) {
|
|
// Get XML document
|
|
function getXML() {
|
|
var i = document.implementation;
|
|
|
|
if (!i || !i.createDocument) {
|
|
// Try IE objects
|
|
try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
|
|
try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
|
|
} else
|
|
return i.createDocument('', '', null);
|
|
};
|
|
|
|
this.doc = getXML();
|
|
|
|
// Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers
|
|
this.valid = tinymce.isOpera || tinymce.isWebKit;
|
|
|
|
this.reset();
|
|
},
|
|
|
|
reset : function() {
|
|
var t = this, d = t.doc;
|
|
|
|
if (d.firstChild)
|
|
d.removeChild(d.firstChild);
|
|
|
|
t.node = d.appendChild(d.createElement("html"));
|
|
},
|
|
|
|
writeStartElement : function(n) {
|
|
var t = this;
|
|
|
|
t.node = t.node.appendChild(t.doc.createElement(n));
|
|
},
|
|
|
|
writeAttribute : function(n, v) {
|
|
if (this.valid)
|
|
v = v.replace(/>/g, '%MCGT%');
|
|
|
|
this.node.setAttribute(n, v);
|
|
},
|
|
|
|
writeEndElement : function() {
|
|
this.node = this.node.parentNode;
|
|
},
|
|
|
|
writeFullEndElement : function() {
|
|
var t = this, n = t.node;
|
|
|
|
n.appendChild(t.doc.createTextNode(""));
|
|
t.node = n.parentNode;
|
|
},
|
|
|
|
writeText : function(v) {
|
|
if (this.valid)
|
|
v = v.replace(/>/g, '%MCGT%');
|
|
|
|
this.node.appendChild(this.doc.createTextNode(v));
|
|
},
|
|
|
|
writeCDATA : function(v) {
|
|
this.node.appendChild(this.doc.createCDATASection(v));
|
|
},
|
|
|
|
writeComment : function(v) {
|
|
// Fix for bug #2035694
|
|
if (tinymce.isIE)
|
|
v = v.replace(/^\-|\-$/g, ' ');
|
|
|
|
this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
|
|
},
|
|
|
|
getContent : function() {
|
|
var h;
|
|
|
|
h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
|
|
h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
|
|
h = h.replace(/ ?\/>/g, ' />');
|
|
|
|
if (this.valid)
|
|
h = h.replace(/\%MCGT%/g, '>');
|
|
|
|
return h;
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
tinymce.create('tinymce.dom.StringWriter', {
|
|
str : null,
|
|
tags : null,
|
|
count : 0,
|
|
settings : null,
|
|
indent : null,
|
|
|
|
StringWriter : function(s) {
|
|
this.settings = tinymce.extend({
|
|
indent_char : ' ',
|
|
indentation : 0
|
|
}, s);
|
|
|
|
this.reset();
|
|
},
|
|
|
|
reset : function() {
|
|
this.indent = '';
|
|
this.str = "";
|
|
this.tags = [];
|
|
this.count = 0;
|
|
},
|
|
|
|
writeStartElement : function(n) {
|
|
this._writeAttributesEnd();
|
|
this.writeRaw('<' + n);
|
|
this.tags.push(n);
|
|
this.inAttr = true;
|
|
this.count++;
|
|
this.elementCount = this.count;
|
|
},
|
|
|
|
writeAttribute : function(n, v) {
|
|
var t = this;
|
|
|
|
t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
|
|
},
|
|
|
|
writeEndElement : function() {
|
|
var n;
|
|
|
|
if (this.tags.length > 0) {
|
|
n = this.tags.pop();
|
|
|
|
if (this._writeAttributesEnd(1))
|
|
this.writeRaw('</' + n + '>');
|
|
|
|
if (this.settings.indentation > 0)
|
|
this.writeRaw('\n');
|
|
}
|
|
},
|
|
|
|
writeFullEndElement : function() {
|
|
if (this.tags.length > 0) {
|
|
this._writeAttributesEnd();
|
|
this.writeRaw('</' + this.tags.pop() + '>');
|
|
|
|
if (this.settings.indentation > 0)
|
|
this.writeRaw('\n');
|
|
}
|
|
},
|
|
|
|
writeText : function(v) {
|
|
this._writeAttributesEnd();
|
|
this.writeRaw(this.encode(v));
|
|
this.count++;
|
|
},
|
|
|
|
writeCDATA : function(v) {
|
|
this._writeAttributesEnd();
|
|
this.writeRaw('<![CDATA[' + v + ']]>');
|
|
this.count++;
|
|
},
|
|
|
|
writeComment : function(v) {
|
|
this._writeAttributesEnd();
|
|
this.writeRaw('<!-- ' + v + '-->');
|
|
this.count++;
|
|
},
|
|
|
|
writeRaw : function(v) {
|
|
this.str += v;
|
|
},
|
|
|
|
encode : function(s) {
|
|
return s.replace(/[<>&"]/g, function(v) {
|
|
switch (v) {
|
|
case '<':
|
|
return '<';
|
|
|
|
case '>':
|
|
return '>';
|
|
|
|
case '&':
|
|
return '&';
|
|
|
|
case '"':
|
|
return '"';
|
|
}
|
|
|
|
return v;
|
|
});
|
|
},
|
|
|
|
getContent : function() {
|
|
return this.str;
|
|
},
|
|
|
|
_writeAttributesEnd : function(s) {
|
|
if (!this.inAttr)
|
|
return;
|
|
|
|
this.inAttr = false;
|
|
|
|
if (s && this.elementCount == this.count) {
|
|
this.writeRaw(' />');
|
|
return false;
|
|
}
|
|
|
|
this.writeRaw('>');
|
|
|
|
return true;
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
// Shorten names
|
|
var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
|
|
|
|
function wildcardToRE(s) {
|
|
return s.replace(/([?+*])/g, '.$1');
|
|
};
|
|
|
|
tinymce.create('tinymce.dom.Serializer', {
|
|
Serializer : function(s) {
|
|
var t = this;
|
|
|
|
t.key = 0;
|
|
t.onPreProcess = new Dispatcher(t);
|
|
t.onPostProcess = new Dispatcher(t);
|
|
|
|
try {
|
|
t.writer = new tinymce.dom.XMLWriter();
|
|
} catch (ex) {
|
|
// IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
|
|
t.writer = new tinymce.dom.StringWriter();
|
|
}
|
|
|
|
// Default settings
|
|
t.settings = s = extend({
|
|
dom : tinymce.DOM,
|
|
valid_nodes : 0,
|
|
node_filter : 0,
|
|
attr_filter : 0,
|
|
invalid_attrs : /^(mce_|_moz_|sizset|sizcache)/,
|
|
closed : /^(br|hr|input|meta|img|link|param|area)$/,
|
|
entity_encoding : 'named',
|
|
entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro',
|
|
valid_elements : '*[*]',
|
|
extended_valid_elements : 0,
|
|
valid_child_elements : 0,
|
|
invalid_elements : 0,
|
|
fix_table_elements : 1,
|
|
fix_list_elements : true,
|
|
fix_content_duplication : true,
|
|
convert_fonts_to_spans : false,
|
|
font_size_classes : 0,
|
|
font_size_style_values : 0,
|
|
apply_source_formatting : 0,
|
|
indent_mode : 'simple',
|
|
indent_char : '\t',
|
|
indent_levels : 1,
|
|
remove_linebreaks : 0,
|
|
remove_redundant_brs : 1,
|
|
element_format : 'xhtml'
|
|
}, s);
|
|
|
|
t.dom = s.dom;
|
|
|
|
if (s.remove_redundant_brs) {
|
|
t.onPostProcess.add(function(se, o) {
|
|
// Remove single BR at end of block elements since they get rendered
|
|
o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
|
|
// Check if it's a single element
|
|
if (/^<br \/>\s*<\//.test(a))
|
|
return '</' + c + '>';
|
|
|
|
return a;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Remove XHTML element endings i.e. produce crap :) XHTML is better
|
|
if (s.element_format == 'html') {
|
|
t.onPostProcess.add(function(se, o) {
|
|
o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
|
|
});
|
|
}
|
|
|
|
if (s.fix_list_elements) {
|
|
t.onPreProcess.add(function(se, o) {
|
|
var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
|
|
|
|
function prevNode(e, n) {
|
|
var a = n.split(','), i;
|
|
|
|
while ((e = e.previousSibling) != null) {
|
|
for (i=0; i<a.length; i++) {
|
|
if (e.nodeName == a[i])
|
|
return e;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
for (x=0; x<a.length; x++) {
|
|
nl = t.dom.select(a[x], o.node);
|
|
|
|
for (i=0; i<nl.length; i++) {
|
|
n = nl[i];
|
|
p = n.parentNode;
|
|
|
|
if (r.test(p.nodeName)) {
|
|
np = prevNode(n, 'LI');
|
|
|
|
if (!np) {
|
|
np = t.dom.create('li');
|
|
np.innerHTML = ' ';
|
|
np.appendChild(n);
|
|
p.insertBefore(np, p.firstChild);
|
|
} else
|
|
np.appendChild(n);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (s.fix_table_elements) {
|
|
t.onPreProcess.add(function(se, o) {
|
|
// Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
|
|
// so Opera users with an older version will have to live with less compaible output not much we can do here
|
|
if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
|
|
each(t.dom.select('p table', o.node).reverse(), function(n) {
|
|
var parent = t.dom.getParent(n.parentNode, 'table,p');
|
|
|
|
if (parent.nodeName != 'TABLE') {
|
|
try {
|
|
t.dom.split(parent, n);
|
|
} catch (ex) {
|
|
// IE can sometimes fire an unknown runtime error so we just ignore it
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
setEntities : function(s) {
|
|
var t = this, a, i, l = {}, re = '', v;
|
|
|
|
// No need to setup more than once
|
|
if (t.entityLookup)
|
|
return;
|
|
|
|
// Build regex and lookup array
|
|
a = s.split(',');
|
|
for (i = 0; i < a.length; i += 2) {
|
|
v = a[i];
|
|
|
|
// Don't add default & " etc.
|
|
if (v == 34 || v == 38 || v == 60 || v == 62)
|
|
continue;
|
|
|
|
l[String.fromCharCode(a[i])] = a[i + 1];
|
|
|
|
v = parseInt(a[i]).toString(16);
|
|
re += '\\u' + '0000'.substring(v.length) + v;
|
|
}
|
|
|
|
if (!re) {
|
|
t.settings.entity_encoding = 'raw';
|
|
return;
|
|
}
|
|
|
|
t.entitiesRE = new RegExp('[' + re + ']', 'g');
|
|
t.entityLookup = l;
|
|
},
|
|
|
|
setValidChildRules : function(s) {
|
|
this.childRules = null;
|
|
this.addValidChildRules(s);
|
|
},
|
|
|
|
addValidChildRules : function(s) {
|
|
var t = this, inst, intr, bloc;
|
|
|
|
if (!s)
|
|
return;
|
|
|
|
inst = 'A|BR|SPAN|BDO|MAP|OBJECT|IMG|TT|I|B|BIG|SMALL|EM|STRONG|DFN|CODE|Q|SAMP|KBD|VAR|CITE|ABBR|ACRONYM|SUB|SUP|#text|#comment';
|
|
intr = 'A|BR|SPAN|BDO|OBJECT|APPLET|IMG|MAP|IFRAME|TT|I|B|U|S|STRIKE|BIG|SMALL|FONT|BASEFONT|EM|STRONG|DFN|CODE|Q|SAMP|KBD|VAR|CITE|ABBR|ACRONYM|SUB|SUP|INPUT|SELECT|TEXTAREA|LABEL|BUTTON|#text|#comment';
|
|
bloc = 'H[1-6]|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|FORM|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP';
|
|
|
|
each(s.split(','), function(s) {
|
|
var p = s.split(/\[|\]/), re;
|
|
|
|
s = '';
|
|
each(p[1].split('|'), function(v) {
|
|
if (s)
|
|
s += '|';
|
|
|
|
switch (v) {
|
|
case '%itrans':
|
|
v = intr;
|
|
break;
|
|
|
|
case '%itrans_na':
|
|
v = intr.substring(2);
|
|
break;
|
|
|
|
case '%istrict':
|
|
v = inst;
|
|
break;
|
|
|
|
case '%istrict_na':
|
|
v = inst.substring(2);
|
|
break;
|
|
|
|
case '%btrans':
|
|
v = bloc;
|
|
break;
|
|
|
|
case '%bstrict':
|
|
v = bloc;
|
|
break;
|
|
}
|
|
|
|
s += v;
|
|
});
|
|
re = new RegExp('^(' + s.toLowerCase() + ')$', 'i');
|
|
|
|
each(p[0].split('/'), function(s) {
|
|
t.childRules = t.childRules || {};
|
|
t.childRules[s] = re;
|
|
});
|
|
});
|
|
|
|
// Build regex
|
|
s = '';
|
|
each(t.childRules, function(v, k) {
|
|
if (s)
|
|
s += '|';
|
|
|
|
s += k;
|
|
});
|
|
|
|
t.parentElementsRE = new RegExp('^(' + s.toLowerCase() + ')$', 'i');
|
|
|
|
/*console.debug(t.parentElementsRE.toString());
|
|
each(t.childRules, function(v) {
|
|
console.debug(v.toString());
|
|
});*/
|
|
},
|
|
|
|
setRules : function(s) {
|
|
var t = this;
|
|
|
|
t._setup();
|
|
t.rules = {};
|
|
t.wildRules = [];
|
|
t.validElements = {};
|
|
|
|
return t.addRules(s);
|
|
},
|
|
|
|
addRules : function(s) {
|
|
var t = this, dr;
|
|
|
|
if (!s)
|
|
return;
|
|
|
|
t._setup();
|
|
|
|
each(s.split(','), function(s) {
|
|
var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
|
|
|
|
// Extend with default rules
|
|
if (dr)
|
|
at = tinymce.extend([], dr.attribs);
|
|
|
|
// Parse attributes
|
|
if (p.length > 1) {
|
|
each(p[1].split('|'), function(s) {
|
|
var ar = {}, i;
|
|
|
|
at = at || [];
|
|
|
|
// Parse attribute rule
|
|
s = s.replace(/::/g, '~');
|
|
s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
|
|
s[2] = s[2].replace(/~/g, ':');
|
|
|
|
// Add required attributes
|
|
if (s[1] == '!') {
|
|
ra = ra || [];
|
|
ra.push(s[2]);
|
|
}
|
|
|
|
// Remove inherited attributes
|
|
if (s[1] == '-') {
|
|
for (i = 0; i <at.length; i++) {
|
|
if (at[i].name == s[2]) {
|
|
at.splice(i, 1);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (s[3]) {
|
|
// Add default attrib values
|
|
case '=':
|
|
ar.defaultVal = s[4] || '';
|
|
break;
|
|
|
|
// Add forced attrib values
|
|
case ':':
|
|
ar.forcedVal = s[4];
|
|
break;
|
|
|
|
// Add validation values
|
|
case '<':
|
|
ar.validVals = s[4].split('?');
|
|
break;
|
|
}
|
|
|
|
if (/[*.?]/.test(s[2])) {
|
|
wat = wat || [];
|
|
ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
|
|
wat.push(ar);
|
|
} else {
|
|
ar.name = s[2];
|
|
at.push(ar);
|
|
}
|
|
|
|
va.push(s[2]);
|
|
});
|
|
}
|
|
|
|
// Handle element names
|
|
each(tn, function(s, i) {
|
|
var pr = s.charAt(0), x = 1, ru = {};
|
|
|
|
// Extend with default rule data
|
|
if (dr) {
|
|
if (dr.noEmpty)
|
|
ru.noEmpty = dr.noEmpty;
|
|
|
|
if (dr.fullEnd)
|
|
ru.fullEnd = dr.fullEnd;
|
|
|
|
if (dr.padd)
|
|
ru.padd = dr.padd;
|
|
}
|
|
|
|
// Handle prefixes
|
|
switch (pr) {
|
|
case '-':
|
|
ru.noEmpty = true;
|
|
break;
|
|
|
|
case '+':
|
|
ru.fullEnd = true;
|
|
break;
|
|
|
|
case '#':
|
|
ru.padd = true;
|
|
break;
|
|
|
|
default:
|
|
x = 0;
|
|
}
|
|
|
|
tn[i] = s = s.substring(x);
|
|
t.validElements[s] = 1;
|
|
|
|
// Add element name or element regex
|
|
if (/[*.?]/.test(tn[0])) {
|
|
ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
|
|
t.wildRules = t.wildRules || {};
|
|
t.wildRules.push(ru);
|
|
} else {
|
|
ru.name = tn[0];
|
|
|
|
// Store away default rule
|
|
if (tn[0] == '@')
|
|
dr = ru;
|
|
|
|
t.rules[s] = ru;
|
|
}
|
|
|
|
ru.attribs = at;
|
|
|
|
if (ra)
|
|
ru.requiredAttribs = ra;
|
|
|
|
if (wat) {
|
|
// Build valid attributes regexp
|
|
s = '';
|
|
each(va, function(v) {
|
|
if (s)
|
|
s += '|';
|
|
|
|
s += '(' + wildcardToRE(v) + ')';
|
|
});
|
|
ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
|
|
ru.wildAttribs = wat;
|
|
}
|
|
});
|
|
});
|
|
|
|
// Build valid elements regexp
|
|
s = '';
|
|
each(t.validElements, function(v, k) {
|
|
if (s)
|
|
s += '|';
|
|
|
|
if (k != '@')
|
|
s += k;
|
|
});
|
|
t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
|
|
|
|
//console.debug(t.validElementsRE.toString());
|
|
//console.dir(t.rules);
|
|
//console.dir(t.wildRules);
|
|
},
|
|
|
|
findRule : function(n) {
|
|
var t = this, rl = t.rules, i, r;
|
|
|
|
t._setup();
|
|
|
|
// Exact match
|
|
r = rl[n];
|
|
if (r)
|
|
return r;
|
|
|
|
// Try wildcards
|
|
rl = t.wildRules;
|
|
for (i = 0; i < rl.length; i++) {
|
|
if (rl[i].nameRE.test(n))
|
|
return rl[i];
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
findAttribRule : function(ru, n) {
|
|
var i, wa = ru.wildAttribs;
|
|
|
|
for (i = 0; i < wa.length; i++) {
|
|
if (wa[i].nameRE.test(n))
|
|
return wa[i];
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
serialize : function(n, o) {
|
|
var h, t = this, doc, oldDoc, impl, selected;
|
|
|
|
t._setup();
|
|
o = o || {};
|
|
o.format = o.format || 'html';
|
|
t.processObj = o;
|
|
|
|
// IE looses the selected attribute on option elements so we need to store it
|
|
// See: http://support.microsoft.com/kb/829907
|
|
if (isIE) {
|
|
selected = [];
|
|
each(n.getElementsByTagName('option'), function(n) {
|
|
var v = t.dom.getAttrib(n, 'selected');
|
|
|
|
selected.push(v ? v : null);
|
|
});
|
|
}
|
|
|
|
n = n.cloneNode(true);
|
|
|
|
// IE looses the selected attribute on option elements so we need to restore it
|
|
if (isIE) {
|
|
each(n.getElementsByTagName('option'), function(n, i) {
|
|
t.dom.setAttrib(n, 'selected', selected[i]);
|
|
});
|
|
}
|
|
|
|
// Nodes needs to be attached to something in WebKit/Opera
|
|
// Older builds of Opera crashes if you attach the node to an document created dynamically
|
|
// and since we can't feature detect a crash we need to sniff the acutal build number
|
|
// This fix will make DOM ranges and make Sizzle happy!
|
|
impl = n.ownerDocument.implementation;
|
|
if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
|
|
// Create an empty HTML document
|
|
doc = impl.createHTMLDocument("");
|
|
|
|
// Add the element or it's children if it's a body element to the new document
|
|
each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
|
|
doc.body.appendChild(doc.importNode(node, true));
|
|
});
|
|
|
|
// Grab first child or body element for serialization
|
|
if (n.nodeName != 'BODY')
|
|
n = doc.body.firstChild;
|
|
else
|
|
n = doc.body;
|
|
|
|
// set the new document in DOMUtils so createElement etc works
|
|
oldDoc = t.dom.doc;
|
|
t.dom.doc = doc;
|
|
}
|
|
|
|
t.key = '' + (parseInt(t.key) + 1);
|
|
|
|
// Pre process
|
|
if (!o.no_events) {
|
|
o.node = n;
|
|
t.onPreProcess.dispatch(t, o);
|
|
}
|
|
|
|
// Serialize HTML DOM into a string
|
|
t.writer.reset();
|
|
t._serializeNode(n, o.getInner);
|
|
|
|
// Post process
|
|
o.content = t.writer.getContent();
|
|
|
|
// Restore the old document if it was changed
|
|
if (oldDoc)
|
|
t.dom.doc = oldDoc;
|
|
|
|
if (!o.no_events)
|
|
t.onPostProcess.dispatch(t, o);
|
|
|
|
t._postProcess(o);
|
|
o.node = null;
|
|
|
|
return tinymce.trim(o.content);
|
|
},
|
|
|
|
// Internal functions
|
|
|
|
_postProcess : function(o) {
|
|
var t = this, s = t.settings, h = o.content, sc = [], p;
|
|
|
|
if (o.format == 'html') {
|
|
// Protect some elements
|
|
p = t._protect({
|
|
content : h,
|
|
patterns : [
|
|
{pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
|
|
{pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
|
|
{pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
|
|
{pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
|
|
{pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
|
|
]
|
|
});
|
|
|
|
h = p.content;
|
|
|
|
// Entity encode
|
|
if (s.entity_encoding !== 'raw')
|
|
h = t._encode(h);
|
|
|
|
// Use BR instead of padded P elements inside editor and use <p> </p> outside editor
|
|
/* if (o.set)
|
|
h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
|
|
else
|
|
h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
|
|
|
|
// Since Gecko and Safari keeps whitespace in the DOM we need to
|
|
// remove it inorder to match other browsers. But I think Gecko and Safari is right.
|
|
// This process is only done when getting contents out from the editor.
|
|
if (!o.set) {
|
|
// We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
|
|
h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1> </p>' : '<p$1> </p>');
|
|
|
|
if (s.remove_linebreaks) {
|
|
h = h.replace(/\r?\n|\r/g, ' ');
|
|
h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
|
|
h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
|
|
h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start
|
|
h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start
|
|
h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, '</$1>'); // Trim block end
|
|
}
|
|
|
|
// Simple indentation
|
|
if (s.apply_source_formatting && s.indent_mode == 'simple') {
|
|
// Add line breaks before and after block elements
|
|
h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
|
|
h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
|
|
h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
|
|
h = h.replace(/\n\n/g, '\n');
|
|
}
|
|
}
|
|
|
|
h = t._unprotect(h, p);
|
|
|
|
// Restore CDATA sections
|
|
h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
|
|
|
|
// Restore the \u00a0 character if raw mode is enabled
|
|
if (s.entity_encoding == 'raw')
|
|
h = h.replace(/<p> <\/p>|<p([^>]+)> <\/p>/g, '<p$1>\u00a0</p>');
|
|
|
|
// Restore noscript elements
|
|
h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
|
|
return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
|
|
});
|
|
}
|
|
|
|
o.content = h;
|
|
},
|
|
|
|
_serializeNode : function(n, inn) {
|
|
var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed;
|
|
|
|
if (!s.node_filter || s.node_filter(n)) {
|
|
switch (n.nodeType) {
|
|
case 1: // Element
|
|
if (n.hasAttribute ? n.hasAttribute('mce_bogus') : n.getAttribute('mce_bogus'))
|
|
return;
|
|
|
|
iv = false;
|
|
hc = n.hasChildNodes();
|
|
nn = n.getAttribute('mce_name') || n.nodeName.toLowerCase();
|
|
|
|
// Add correct prefix on IE
|
|
if (isIE) {
|
|
if (n.scopeName !== 'HTML' && n.scopeName !== 'html')
|
|
nn = n.scopeName + ':' + nn;
|
|
}
|
|
|
|
// Remove mce prefix on IE needed for the abbr element
|
|
if (nn.indexOf('mce:') === 0)
|
|
nn = nn.substring(4);
|
|
|
|
// Check if valid
|
|
if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inn) {
|
|
iv = true;
|
|
break;
|
|
}
|
|
|
|
if (isIE) {
|
|
// Fix IE content duplication (DOM can have multiple copies of the same node)
|
|
if (s.fix_content_duplication) {
|
|
if (n.mce_serialized == t.key)
|
|
return;
|
|
|
|
n.mce_serialized = t.key;
|
|
}
|
|
|
|
// IE sometimes adds a / infront of the node name
|
|
if (nn.charAt(0) == '/')
|
|
nn = nn.substring(1);
|
|
} else if (isGecko) {
|
|
// Ignore br elements
|
|
if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
|
|
return;
|
|
}
|
|
|
|
// Check if valid child
|
|
if (t.childRules) {
|
|
if (t.parentElementsRE.test(t.elementName)) {
|
|
if (!t.childRules[t.elementName].test(nn)) {
|
|
iv = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
t.elementName = nn;
|
|
}
|
|
|
|
ru = t.findRule(nn);
|
|
nn = ru.name || nn;
|
|
closed = s.closed.test(nn);
|
|
|
|
// Skip empty nodes or empty node name in IE
|
|
if ((!hc && ru.noEmpty) || (isIE && !nn)) {
|
|
iv = true;
|
|
break;
|
|
}
|
|
|
|
// Check required
|
|
if (ru.requiredAttribs) {
|
|
a = ru.requiredAttribs;
|
|
|
|
for (i = a.length - 1; i >= 0; i--) {
|
|
if (this.dom.getAttrib(n, a[i]) !== '')
|
|
break;
|
|
}
|
|
|
|
// None of the required was there
|
|
if (i == -1) {
|
|
iv = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
w.writeStartElement(nn);
|
|
|
|
// Add ordered attributes
|
|
if (ru.attribs) {
|
|
for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
|
|
a = at[i];
|
|
v = t._getAttrib(n, a);
|
|
|
|
if (v !== null)
|
|
w.writeAttribute(a.name, v);
|
|
}
|
|
}
|
|
|
|
// Add wild attributes
|
|
if (ru.validAttribsRE) {
|
|
at = t.dom.getAttribs(n);
|
|
for (i=at.length-1; i>-1; i--) {
|
|
no = at[i];
|
|
|
|
if (no.specified) {
|
|
a = no.nodeName.toLowerCase();
|
|
|
|
if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
|
|
continue;
|
|
|
|
ar = t.findAttribRule(ru, a);
|
|
v = t._getAttrib(n, ar, a);
|
|
|
|
if (v !== null)
|
|
w.writeAttribute(a, v);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write text from script
|
|
if (nn === 'script' && tinymce.trim(n.innerHTML)) {
|
|
w.writeText('// '); // Padd it with a comment so it will parse on older browsers
|
|
w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
|
|
hc = false;
|
|
break;
|
|
}
|
|
|
|
// Padd empty nodes with a
|
|
if (ru.padd) {
|
|
// If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
|
|
if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
|
|
if (cn.hasAttribute ? cn.hasAttribute('mce_bogus') : cn.getAttribute('mce_bogus'))
|
|
w.writeText('\u00a0');
|
|
} else if (!hc)
|
|
w.writeText('\u00a0'); // No children then padd it
|
|
}
|
|
|
|
break;
|
|
|
|
case 3: // Text
|
|
// Check if valid child
|
|
if (t.childRules && t.parentElementsRE.test(t.elementName)) {
|
|
if (!t.childRules[t.elementName].test(n.nodeName))
|
|
return;
|
|
}
|
|
|
|
return w.writeText(n.nodeValue);
|
|
|
|
case 4: // CDATA
|
|
return w.writeCDATA(n.nodeValue);
|
|
|
|
case 8: // Comment
|
|
return w.writeComment(n.nodeValue);
|
|
}
|
|
} else if (n.nodeType == 1)
|
|
hc = n.hasChildNodes();
|
|
|
|
if (hc && !closed) {
|
|
cn = n.firstChild;
|
|
|
|
while (cn) {
|
|
t._serializeNode(cn);
|
|
t.elementName = nn;
|
|
cn = cn.nextSibling;
|
|
}
|
|
}
|
|
|
|
// Write element end
|
|
if (!iv) {
|
|
if (!closed)
|
|
w.writeFullEndElement();
|
|
else
|
|
w.writeEndElement();
|
|
}
|
|
},
|
|
|
|
_protect : function(o) {
|
|
var t = this;
|
|
|
|
o.items = o.items || [];
|
|
|
|
function enc(s) {
|
|
return s.replace(/[\r\n\\]/g, function(c) {
|
|
if (c === '\n')
|
|
return '\\n';
|
|
else if (c === '\\')
|
|
return '\\\\';
|
|
|
|
return '\\r';
|
|
});
|
|
};
|
|
|
|
function dec(s) {
|
|
return s.replace(/\\[\\rn]/g, function(c) {
|
|
if (c === '\\n')
|
|
return '\n';
|
|
else if (c === '\\\\')
|
|
return '\\';
|
|
|
|
return '\r';
|
|
});
|
|
};
|
|
|
|
each(o.patterns, function(p) {
|
|
o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
|
|
b = dec(b);
|
|
|
|
if (p.encode)
|
|
b = t._encode(b);
|
|
|
|
o.items.push(b);
|
|
return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
|
|
}));
|
|
});
|
|
|
|
return o;
|
|
},
|
|
|
|
_unprotect : function(h, o) {
|
|
h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
|
|
return o.items[parseInt(b)];
|
|
});
|
|
|
|
o.items = [];
|
|
|
|
return h;
|
|
},
|
|
|
|
_encode : function(h) {
|
|
var t = this, s = t.settings, l;
|
|
|
|
// Entity encode
|
|
if (s.entity_encoding !== 'raw') {
|
|
if (s.entity_encoding.indexOf('named') != -1) {
|
|
t.setEntities(s.entities);
|
|
l = t.entityLookup;
|
|
|
|
h = h.replace(t.entitiesRE, function(a) {
|
|
var v;
|
|
|
|
if (v = l[a])
|
|
a = '&' + v + ';';
|
|
|
|
return a;
|
|
});
|
|
}
|
|
|
|
if (s.entity_encoding.indexOf('numeric') != -1) {
|
|
h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
|
|
return '&#' + a.charCodeAt(0) + ';';
|
|
});
|
|
}
|
|
}
|
|
|
|
return h;
|
|
},
|
|
|
|
_setup : function() {
|
|
var t = this, s = this.settings;
|
|
|
|
if (t.done)
|
|
return;
|
|
|
|
t.done = 1;
|
|
|
|
t.setRules(s.valid_elements);
|
|
t.addRules(s.extended_valid_elements);
|
|
t.addValidChildRules(s.valid_child_elements);
|
|
|
|
if (s.invalid_elements)
|
|
t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
|
|
|
|
if (s.attrib_value_filter)
|
|
t.attribValueFilter = s.attribValueFilter;
|
|
},
|
|
|
|
_getAttrib : function(n, a, na) {
|
|
var i, v;
|
|
|
|
na = na || a.name;
|
|
|
|
if (a.forcedVal && (v = a.forcedVal)) {
|
|
if (v === '{$uid}')
|
|
return this.dom.uniqueId();
|
|
|
|
return v;
|
|
}
|
|
|
|
v = this.dom.getAttrib(n, na);
|
|
|
|
switch (na) {
|
|
case 'rowspan':
|
|
case 'colspan':
|
|
// Whats the point? Remove usless attribute value
|
|
if (v == '1')
|
|
v = '';
|
|
|
|
break;
|
|
}
|
|
|
|
if (this.attribValueFilter)
|
|
v = this.attribValueFilter(na, v, n);
|
|
|
|
if (a.validVals) {
|
|
for (i = a.validVals.length - 1; i >= 0; i--) {
|
|
if (v == a.validVals[i])
|
|
break;
|
|
}
|
|
|
|
if (i == -1)
|
|
return null;
|
|
}
|
|
|
|
if (v === '' && typeof(a.defaultVal) != 'undefined') {
|
|
v = a.defaultVal;
|
|
|
|
if (v === '{$uid}')
|
|
return this.dom.uniqueId();
|
|
|
|
return v;
|
|
} else {
|
|
// Remove internal mceItemXX classes when content is extracted from editor
|
|
if (na == 'class' && this.processObj.get)
|
|
v = v.replace(/\s?mceItem\w+\s?/g, '');
|
|
}
|
|
|
|
if (v === '')
|
|
return null;
|
|
|
|
|
|
return v;
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
var each = tinymce.each, Event = tinymce.dom.Event;
|
|
|
|
tinymce.create('tinymce.dom.ScriptLoader', {
|
|
ScriptLoader : function(s) {
|
|
this.settings = s || {};
|
|
this.queue = [];
|
|
this.lookup = {};
|
|
},
|
|
|
|
isDone : function(u) {
|
|
return this.lookup[u] ? this.lookup[u].state == 2 : 0;
|
|
},
|
|
|
|
markDone : function(u) {
|
|
this.lookup[u] = {state : 2, url : u};
|
|
},
|
|
|
|
add : function(u, cb, s, pr) {
|
|
var t = this, lo = t.lookup, o;
|
|
|
|
if (o = lo[u]) {
|
|
// Is loaded fire callback
|
|
if (cb && o.state == 2)
|
|
cb.call(s || this);
|
|
|
|
return o;
|
|
}
|
|
|
|
o = {state : 0, url : u, func : cb, scope : s || this};
|
|
|
|
if (pr)
|
|
t.queue.unshift(o);
|
|
else
|
|
t.queue.push(o);
|
|
|
|
lo[u] = o;
|
|
|
|
return o;
|
|
},
|
|
|
|
load : function(u, cb, s) {
|
|
var t = this, o;
|
|
|
|
if (o = t.lookup[u]) {
|
|
// Is loaded fire callback
|
|
if (cb && o.state == 2)
|
|
cb.call(s || t);
|
|
|
|
return o;
|
|
}
|
|
|
|
function loadScript(u) {
|
|
if (Event.domLoaded || t.settings.strict_mode) {
|
|
tinymce.util.XHR.send({
|
|
url : tinymce._addVer(u),
|
|
error : t.settings.error,
|
|
async : false,
|
|
success : function(co) {
|
|
t.eval(co);
|
|
}
|
|
});
|
|
} else
|
|
document.write('<script type="text/javascript" src="' + tinymce._addVer(u) + '"></script>');
|
|
};
|
|
|
|
if (!tinymce.is(u, 'string')) {
|
|
each(u, function(u) {
|
|
loadScript(u);
|
|
});
|
|
|
|
if (cb)
|
|
cb.call(s || t);
|
|
} else {
|
|
loadScript(u);
|
|
|
|
if (cb)
|
|
cb.call(s || t);
|
|
}
|
|
},
|
|
|
|
loadQueue : function(cb, s) {
|
|
var t = this;
|
|
|
|
if (!t.queueLoading) {
|
|
t.queueLoading = 1;
|
|
t.queueCallbacks = [];
|
|
|
|
t.loadScripts(t.queue, function() {
|
|
t.queueLoading = 0;
|
|
|
|
if (cb)
|
|
cb.call(s || t);
|
|
|
|
each(t.queueCallbacks, function(o) {
|
|
o.func.call(o.scope);
|
|
});
|
|
});
|
|
} else if (cb)
|
|
t.queueCallbacks.push({func : cb, scope : s || t});
|
|
},
|
|
|
|
eval : function(co) {
|
|
var w = window;
|
|
|
|
// Evaluate script
|
|
if (!w.execScript) {
|
|
try {
|
|
eval.call(w, co);
|
|
} catch (ex) {
|
|
eval(co, w); // Firefox 3.0a8
|
|
}
|
|
} else
|
|
w.execScript(co); // IE
|
|
},
|
|
|
|
loadScripts : function(sc, cb, s) {
|
|
var t = this, lo = t.lookup;
|
|
|
|
function done(o) {
|
|
o.state = 2; // Has been loaded
|
|
|
|
// Run callback
|
|
if (o.func)
|
|
o.func.call(o.scope || t);
|
|
};
|
|
|
|
function allDone() {
|
|
var l;
|
|
|
|
// Check if all files are loaded
|
|
l = sc.length;
|
|
each(sc, function(o) {
|
|
o = lo[o.url];
|
|
|
|
if (o.state === 2) {// It has finished loading
|
|
done(o);
|
|
l--;
|
|
} else
|
|
load(o);
|
|
});
|
|
|
|
// They are all loaded
|
|
if (l === 0 && cb) {
|
|
cb.call(s || t);
|
|
cb = 0;
|
|
}
|
|
};
|
|
|
|
function load(o) {
|
|
if (o.state > 0)
|
|
return;
|
|
|
|
o.state = 1; // Is loading
|
|
|
|
tinymce.dom.ScriptLoader.loadScript(o.url, function() {
|
|
done(o);
|
|
allDone();
|
|
});
|
|
|
|
/*
|
|
tinymce.util.XHR.send({
|
|
url : o.url,
|
|
error : t.settings.error,
|
|
success : function(co) {
|
|
t.eval(co);
|
|
done(o);
|
|
allDone();
|
|
}
|
|
});
|
|
*/
|
|
};
|
|
|
|
each(sc, function(o) {
|
|
var u = o.url;
|
|
|
|
// Add to queue if needed
|
|
if (!lo[u]) {
|
|
lo[u] = o;
|
|
t.queue.push(o);
|
|
} else
|
|
o = lo[u];
|
|
|
|
// Is already loading or has been loaded
|
|
if (o.state > 0)
|
|
return;
|
|
|
|
if (!Event.domLoaded && !t.settings.strict_mode) {
|
|
var ix, ol = '';
|
|
|
|
// Add onload events
|
|
if (cb || o.func) {
|
|
o.state = 1; // Is loading
|
|
|
|
ix = tinymce.dom.ScriptLoader._addOnLoad(function() {
|
|
done(o);
|
|
allDone();
|
|
});
|
|
|
|
if (tinymce.isIE)
|
|
ol = ' onreadystatechange="';
|
|
else
|
|
ol = ' onload="';
|
|
|
|
ol += 'tinymce.dom.ScriptLoader._onLoad(this,\'' + u + '\',' + ix + ');"';
|
|
}
|
|
|
|
document.write('<script type="text/javascript" src="' + tinymce._addVer(u) + '"' + ol + '></script>');
|
|
|
|
if (!o.func)
|
|
done(o);
|
|
} else
|
|
load(o);
|
|
});
|
|
|
|
allDone();
|
|
},
|
|
|
|
// Static methods
|
|
'static' : {
|
|
_addOnLoad : function(f) {
|
|
var t = this;
|
|
|
|
t._funcs = t._funcs || [];
|
|
t._funcs.push(f);
|
|
|
|
return t._funcs.length - 1;
|
|
},
|
|
|
|
_onLoad : function(e, u, ix) {
|
|
if (!tinymce.isIE || e.readyState == 'complete')
|
|
this._funcs[ix].call(this);
|
|
},
|
|
|
|
loadScript : function(u, cb) {
|
|
var id = tinymce.DOM.uniqueId(), e;
|
|
|
|
function done() {
|
|
Event.clear(id);
|
|
tinymce.DOM.remove(id);
|
|
|
|
if (cb) {
|
|
cb.call(document, u);
|
|
cb = 0;
|
|
}
|
|
};
|
|
|
|
if (tinymce.isIE) {
|
|
/* Event.add(e, 'readystatechange', function(e) {
|
|
if (e.target && e.target.readyState == 'complete')
|
|
done();
|
|
});*/
|
|
|
|
tinymce.util.XHR.send({
|
|
url : tinymce._addVer(u),
|
|
async : false,
|
|
success : function(co) {
|
|
window.execScript(co);
|
|
done();
|
|
}
|
|
});
|
|
} else {
|
|
e = tinymce.DOM.create('script', {id : id, type : 'text/javascript', src : tinymce._addVer(u)});
|
|
Event.add(e, 'load', done);
|
|
|
|
// Check for head or body
|
|
(document.getElementsByTagName('head')[0] || document.body).appendChild(e);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Global script loader
|
|
tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
// Shorten class names
|
|
var DOM = tinymce.DOM, is = tinymce.is;
|
|
|
|
tinymce.create('tinymce.ui.Control', {
|
|
Control : function(id, s) {
|
|
this.id = id;
|
|
this.settings = s = s || {};
|
|
this.rendered = false;
|
|
this.onRender = new tinymce.util.Dispatcher(this);
|
|
this.classPrefix = '';
|
|
this.scope = s.scope || this;
|
|
this.disabled = 0;
|
|
this.active = 0;
|
|
},
|
|
|
|
setDisabled : function(s) {
|
|
var e;
|
|
|
|
if (s != this.disabled) {
|
|
e = DOM.get(this.id);
|
|
|
|
// Add accessibility title for unavailable actions
|
|
if (e && this.settings.unavailable_prefix) {
|
|
if (s) {
|
|
this.prevTitle = e.title;
|
|
e.title = this.settings.unavailable_prefix + ": " + e.title;
|
|
} else
|
|
e.title = this.prevTitle;
|
|
}
|
|
|
|
this.setState('Disabled', s);
|
|
this.setState('Enabled', !s);
|
|
this.disabled = s;
|
|
}
|
|
},
|
|
|
|
isDisabled : function() {
|
|
return this.disabled;
|
|
},
|
|
|
|
setActive : function(s) {
|
|
if (s != this.active) {
|
|
this.setState('Active', s);
|
|
this.active = s;
|
|
}
|
|
},
|
|
|
|
isActive : function() {
|
|
return this.active;
|
|
},
|
|
|
|
setState : function(c, s) {
|
|
var n = DOM.get(this.id);
|
|
|
|
c = this.classPrefix + c;
|
|
|
|
if (s)
|
|
DOM.addClass(n, c);
|
|
else
|
|
DOM.removeClass(n, c);
|
|
},
|
|
|
|
isRendered : function() {
|
|
return this.rendered;
|
|
},
|
|
|
|
renderHTML : function() {
|
|
},
|
|
|
|
renderTo : function(n) {
|
|
DOM.setHTML(n, this.renderHTML());
|
|
},
|
|
|
|
postRender : function() {
|
|
var t = this, b;
|
|
|
|
// Set pending states
|
|
if (is(t.disabled)) {
|
|
b = t.disabled;
|
|
t.disabled = -1;
|
|
t.setDisabled(b);
|
|
}
|
|
|
|
if (is(t.active)) {
|
|
b = t.active;
|
|
t.active = -1;
|
|
t.setActive(b);
|
|
}
|
|
},
|
|
|
|
remove : function() {
|
|
DOM.remove(this.id);
|
|
this.destroy();
|
|
},
|
|
|
|
destroy : function() {
|
|
tinymce.dom.Event.clear(this.id);
|
|
}
|
|
});
|
|
})(tinymce);tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
|
|
Container : function(id, s) {
|
|
this.parent(id, s);
|
|
|
|
this.controls = [];
|
|
|
|
this.lookup = {};
|
|
},
|
|
|
|
add : function(c) {
|
|
this.lookup[c.id] = c;
|
|
this.controls.push(c);
|
|
|
|
return c;
|
|
},
|
|
|
|
get : function(n) {
|
|
return this.lookup[n];
|
|
}
|
|
});
|
|
|
|
tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
|
|
Separator : function(id, s) {
|
|
this.parent(id, s);
|
|
this.classPrefix = 'mceSeparator';
|
|
},
|
|
|
|
renderHTML : function() {
|
|
return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
|
|
}
|
|
});
|
|
(function(tinymce) {
|
|
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
|
|
|
|
tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
|
|
MenuItem : function(id, s) {
|
|
this.parent(id, s);
|
|
this.classPrefix = 'mceMenuItem';
|
|
},
|
|
|
|
setSelected : function(s) {
|
|
this.setState('Selected', s);
|
|
this.selected = s;
|
|
},
|
|
|
|
isSelected : function() {
|
|
return this.selected;
|
|
},
|
|
|
|
postRender : function() {
|
|
var t = this;
|
|
|
|
t.parent();
|
|
|
|
// Set pending state
|
|
if (is(t.selected))
|
|
t.setSelected(t.selected);
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
|
|
|
|
tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
|
|
Menu : function(id, s) {
|
|
var t = this;
|
|
|
|
t.parent(id, s);
|
|
t.items = {};
|
|
t.collapsed = false;
|
|
t.menuCount = 0;
|
|
t.onAddItem = new tinymce.util.Dispatcher(this);
|
|
},
|
|
|
|
expand : function(d) {
|
|
var t = this;
|
|
|
|
if (d) {
|
|
walk(t, function(o) {
|
|
if (o.expand)
|
|
o.expand();
|
|
}, 'items', t);
|
|
}
|
|
|
|
t.collapsed = false;
|
|
},
|
|
|
|
collapse : function(d) {
|
|
var t = this;
|
|
|
|
if (d) {
|
|
walk(t, function(o) {
|
|
if (o.collapse)
|
|
o.collapse();
|
|
}, 'items', t);
|
|
}
|
|
|
|
t.collapsed = true;
|
|
},
|
|
|
|
isCollapsed : function() {
|
|
return this.collapsed;
|
|
},
|
|
|
|
add : function(o) {
|
|
if (!o.settings)
|
|
o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
|
|
|
|
this.onAddItem.dispatch(this, o);
|
|
|
|
return this.items[o.id] = o;
|
|
},
|
|
|
|
addSeparator : function() {
|
|
return this.add({separator : true});
|
|
},
|
|
|
|
addMenu : function(o) {
|
|
if (!o.collapse)
|
|
o = this.createMenu(o);
|
|
|
|
this.menuCount++;
|
|
|
|
return this.add(o);
|
|
},
|
|
|
|
hasMenus : function() {
|
|
return this.menuCount !== 0;
|
|
},
|
|
|
|
remove : function(o) {
|
|
delete this.items[o.id];
|
|
},
|
|
|
|
removeAll : function() {
|
|
var t = this;
|
|
|
|
walk(t, function(o) {
|
|
if (o.removeAll)
|
|
o.removeAll();
|
|
else
|
|
o.remove();
|
|
|
|
o.destroy();
|
|
}, 'items', t);
|
|
|
|
t.items = {};
|
|
},
|
|
|
|
createMenu : function(o) {
|
|
var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
|
|
|
|
m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
|
|
|
|
return m;
|
|
}
|
|
});
|
|
})(tinymce);(function(tinymce) {
|
|
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
|
|
|
|
tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
|
|
DropMenu : function(id, s) {
|
|
s = s || {};
|
|
s.container = s.container || DOM.doc.body;
|
|
s.offset_x = s.offset_x || 0;
|
|
s.offset_y = s.offset_y || 0;
|
|
s.vp_offset_x = s.vp_offset_x || 0;
|
|
s.vp_offset_y = s.vp_offset_y || 0;
|
|
|
|
if (is(s.icons) && !s.icons)
|
|
s['class'] += ' mceNoIcons';
|
|
|
|
this.parent(id, s);
|
|
this.onShowMenu = new tinymce.util.Dispatcher(this);
|
|
this.onHideMenu = new tinymce.util.Dispatcher(this);
|
|
this.classPrefix = 'mceMenu';
|
|
},
|
|
|
|
createMenu : function(s) {
|
|
var t = this, cs = t.settings, m;
|
|
|
|
s.container = s.container || cs.container;
|
|
s.parent = t;
|
|
s.constrain = s.constrain || cs.constrain;
|
|
s['class'] = s['class'] || cs['class'];
|
|
s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
|
|
s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
|
|
m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
|
|
|
|
m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
|
|
|
|
return m;
|
|
},
|
|
|
|
update : function() {
|
|
var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
|
|
|
|
tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
|
|
th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
|
|
|
|
if (!DOM.boxModel)
|
|
t.element.setStyles({width : tw + 2, height : th + 2});
|
|
else
|
|
t.element.setStyles({width : tw, height : th});
|
|
|
|
if (s.max_width)
|
|
DOM.setStyle(co, 'width', tw);
|
|
|
|
if (s.max_height) {
|
|
DOM.setStyle(co, 'height', th);
|
|
|
|
if (tb.clientHeight < s.max_height)
|
|
DOM.setStyle(co, 'overflow', 'hidden');
|
|
}
|
|
},
|
|
|
|
showMenu : function(x, y, px) {
|
|
var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
|
|
|
|
t.collapse(1);
|
|
|
|
if (t.isMenuVisible)
|
|
return;
|
|
|
|
if (!t.rendered) {
|
|
co = DOM.add(t.settings.container, t.renderNode());
|
|
|
|
each(t.items, function(o) {
|
|
o.postRender();
|
|
});
|
|
|
|
t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
|
|
} else
|
|
co = DOM.get('menu_' + t.id);
|
|
|
|
// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
|
|
if (!tinymce.isOpera)
|
|
DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
|
|
|
|
DOM.show(co);
|
|
t.update();
|
|
|
|
x += s.offset_x || 0;
|
|
y += s.offset_y || 0;
|
|
vp.w -= 4;
|
|
vp.h -= 4;
|
|
|
|
// Move inside viewport if not submenu
|
|
if (s.constrain) {
|
|
w = co.clientWidth - ot;
|
|
h = co.clientHeight - ot;
|
|
mx = vp.x + vp.w;
|
|
my = vp.y + vp.h;
|
|
|
|
if ((x + s.vp_offset_x + w) > mx)
|
|
x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
|
|
|
|
if ((y + s.vp_offset_y + h) > my)
|
|
y = Math.max(0, (my - s.vp_offset_y) - h);
|
|
}
|
|
|
|
DOM.setStyles(co, {left : x , top : y});
|
|
t.element.update();
|
|
|
|
t.isMenuVisible = 1;
|
|
t.mouseClickFunc = Event.add(co, 'click', function(e) {
|
|
var m;
|
|
|
|
e = e.target;
|
|
|
|
if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
|
|
m = t.items[e.id];
|
|
|
|
if (m.isDisabled())
|
|
return;
|
|
|
|
dm = t;
|
|
|
|
while (dm) {
|
|
if (dm.hideMenu)
|
|
dm.hideMenu();
|
|
|
|
dm = dm.settings.parent;
|
|
}
|
|
|
|
if (m.settings.onclick)
|
|
m.settings.onclick(e);
|
|
|
|
return Event.cancel(e); // Cancel to fix onbeforeunload problem
|
|
}
|
|
});
|
|
|
|
if (t.hasMenus()) {
|
|
t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
|
|
var m, r, mi;
|
|
|
|
e = e.target;
|
|
if (e && (e = DOM.getParent(e, 'tr'))) {
|
|
m = t.items[e.id];
|
|
|
|
if (t.lastMenu)
|
|
t.lastMenu.collapse(1);
|
|
|
|
if (m.isDisabled())
|
|
return;
|
|
|
|
if (e && DOM.hasClass(e, cp + 'ItemSub')) {
|
|
//p = DOM.getPos(s.container);
|
|
r = DOM.getRect(e);
|
|
m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
|
|
t.lastMenu = m;
|
|
DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
t.onShowMenu.dispatch(t);
|
|
|
|
if (s.keyboard_focus) {
|
|
Event.add(co, 'keydown', t._keyHandler, t);
|
|
DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
|
|
t._focusIdx = 0;
|
|
}
|
|
},
|
|
|
|
hideMenu : function(c) {
|
|
var t = this, co = DOM.get('menu_' + t.id), e;
|
|
|
|
if (!t.isMenuVisible)
|
|
return;
|
|
|
|
Event.remove(co, 'mouseover', t.mouseOverFunc);
|
|
Event.remove(co, 'click', t.mouseClickFunc);
|
|
Event.remove(co, 'keydown', t._keyHandler);
|
|
DOM.hide(co);
|
|
t.isMenuVisible = 0;
|
|
|
|
if (!c)
|
|
t.collapse(1);
|
|
|
|
if (t.element)
|
|
t.element.hide();
|
|
|
|
if (e = DOM.get(t.id))
|
|
DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
|
|
|
|
t.onHideMenu.dispatch(t);
|
|
},
|
|
|
|
add : function(o) {
|
|
var t = this, co;
|
|
|
|
o = t.parent(o);
|
|
|
|
if (t.isRendered && (co = DOM.get('menu_' + t.id)))
|
|
t._add(DOM.select('tbody', co)[0], o);
|
|
|
|
return o;
|
|
},
|
|
|
|
collapse : function(d) {
|
|
this.parent(d);
|
|
this.hideMenu(1);
|
|
},
|
|
|
|
remove : function(o) {
|
|
DOM.remove(o.id);
|
|
this.destroy();
|
|
|
|
return this.parent(o);
|
|
},
|
|
|
|
destroy : function() {
|
|
var t = this, co = DOM.get('menu_' + t.id);
|
|
|
|
Event.remove(co, 'mouseover', t.mouseOverFunc);
|
|
Event.remove(co, 'click', t.mouseClickFunc);
|
|
|
|
if (t.element)
|
|
t.element.remove();
|
|
|
|
DOM.remove(co);
|
|
},
|
|
|
|
renderNode : function() {
|
|
var t = this, s = t.settings, n, tb, co, w;
|
|
|
|
w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
|
|
co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
|
|
t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
|
|
|
|
if (s.menu_line)
|
|
DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
|
|
|
|
// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
|
|
n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
|
|
tb = DOM.add(n, 'tbody');
|
|
|
|
each(t.items, function(o) {
|
|
t._add(tb, o);
|
|
});
|
|
|
|
t.rendered = true;
|
|
|
|
return w;
|
|
},
|
|
|
|
// Internal functions
|
|
|
|
_keyHandler : function(e) {
|
|
var t = this, kc = e.keyCode;
|
|
|
|
function focus(d) {
|
|
var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
|
|
|
|
if (e) {
|
|
t._focusIdx = i;
|
|
e.focus();
|
|
}
|
|
};
|
|
|
|
switch (kc) {
|
|
case 38:
|
|
focus(-1); // Select first link
|
|
return;
|
|
|
|
case 40:
|
|
focus(1);
|
|
return;
|
|
|
|
case 13:
|
|
return;
|
|
|
|
case 27:
|
|
return this.hideMenu();
|
|
}
|
|
},
|
|
|
|
_add : function(tb, o) {
|
|
var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
|
|
|
|
if (s.separator) {
|
|
ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
|
|
DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
|
|
|
|
if (n = ro.previousSibling)
|
|
DOM.addClass(n, 'mceLast');
|
|
|
|
return;
|
|
}
|
|
|
|
n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
|
|
n = it = DOM.add(n, 'td');
|
|
n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
|
|
|
|
DOM.addClass(it, s['class']);
|
|
// n = DOM.add(n, 'span', {'class' : 'item'});
|
|
|
|
ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
|
|
|
|
if (s.icon_src)
|
|
DOM.add(ic, 'img', {src : s.icon_src});
|
|
|
|
n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
|
|
|
|
if (o.settings.style)
|
|
DOM.setAttrib(n, 'style', o.settings.style);
|
|
|
|
if (tb.childNodes.length == 1)
|
|
DOM.addClass(ro, 'mceFirst');
|
|
|
|
if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
|
|
DOM.addClass(ro, 'mceFirst');
|
|
|
|
if (o.collapse)
|
|
DOM.addClass(ro, cp + 'ItemSub');
|
|
|
|
if (n = ro.previousSibling)
|
|
DOM.removeClass(n, 'mceLast');
|
|
|
|
DOM.addClass(ro, 'mceLast');
|
|
}
|
|
});
|
|
})(tinymce);(function(tinymce) {
|
|
var DOM = tinymce.DOM;
|
|
|
|
tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
|
|
Button : function(id, s) {
|
|
this.parent(id, s);
|
|
this.classPrefix = 'mceButton';
|
|
},
|
|
|
|
renderHTML : function() {
|
|
var cp = this.classPrefix, s = this.settings, h, l;
|
|
|
|
l = DOM.encode(s.label || '');
|
|
h = '<a id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" title="' + DOM.encode(s.title) + '">';
|
|
|
|
if (s.image)
|
|
h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
|
|
else
|
|
h += '<span class="mceIcon ' + s['class'] + '"><div style="clear: none; width: 1px; height: 1px; filter: alpha(opacity=0.01); color: transparent; overflow: hidden;">' + s.title + '</div></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
|
|
|
|
return h;
|
|
},
|
|
|
|
postRender : function() {
|
|
var t = this, s = t.settings;
|
|
|
|
tinymce.dom.Event.add(t.id, 'click', function(e) {
|
|
if (!t.isDisabled())
|
|
return s.onclick.call(s.scope, e);
|
|
});
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
|
|
|
|
tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
|
|
ListBox : function(id, s) {
|
|
var t = this;
|
|
|
|
t.parent(id, s);
|
|
|
|
t.items = [];
|
|
|
|
t.onChange = new Dispatcher(t);
|
|
|
|
t.onPostRender = new Dispatcher(t);
|
|
|
|
t.onAdd = new Dispatcher(t);
|
|
|
|
t.onRenderMenu = new tinymce.util.Dispatcher(this);
|
|
|
|
t.classPrefix = 'mceListBox';
|
|
},
|
|
|
|
select : function(va) {
|
|
var t = this, fv, f;
|
|
|
|
if (va == undefined)
|
|
return t.selectByIndex(-1);
|
|
|
|
// Is string or number make function selector
|
|
if (va && va.call)
|
|
f = va;
|
|
else {
|
|
f = function(v) {
|
|
return v == va;
|
|
};
|
|
}
|
|
|
|
// Do we need to do something?
|
|
if (va != t.selectedValue) {
|
|
// Find item
|
|
each(t.items, function(o, i) {
|
|
if (f(o.value)) {
|
|
fv = 1;
|
|
t.selectByIndex(i);
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if (!fv)
|
|
t.selectByIndex(-1);
|
|
}
|
|
},
|
|
|
|
selectByIndex : function(idx) {
|
|
var t = this, e, o;
|
|
|
|
if (idx != t.selectedIndex) {
|
|
e = DOM.get(t.id + '_text');
|
|
o = t.items[idx];
|
|
|
|
if (o) {
|
|
t.selectedValue = o.value;
|
|
t.selectedIndex = idx;
|
|
DOM.setHTML(e, DOM.encode(o.title));
|
|
DOM.removeClass(e, 'mceTitle');
|
|
} else {
|
|
DOM.setHTML(e, DOM.encode(t.settings.title));
|
|
DOM.addClass(e, 'mceTitle');
|
|
t.selectedValue = t.selectedIndex = null;
|
|
}
|
|
|
|
e = 0;
|
|
}
|
|
},
|
|
|
|
add : function(n, v, o) {
|
|
var t = this;
|
|
|
|
o = o || {};
|
|
o = tinymce.extend(o, {
|
|
title : n,
|
|
value : v
|
|
});
|
|
|
|
t.items.push(o);
|
|
t.onAdd.dispatch(t, o);
|
|
},
|
|
|
|
getLength : function() {
|
|
return this.items.length;
|
|
},
|
|
|
|
renderHTML : function() {
|
|
var h = '', t = this, s = t.settings, cp = t.classPrefix;
|
|
|
|
h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
|
|
h += '<td>' + DOM.createHTML('a', {id : t.id + '_text', href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
|
|
h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span></span>') + '</td>';
|
|
h += '</tr></tbody></table>';
|
|
|
|
return h;
|
|
},
|
|
|
|
showMenu : function() {
|
|
var t = this, p1, p2, e = DOM.get(this.id), m;
|
|
|
|
if (t.isDisabled() || t.items.length == 0)
|
|
return;
|
|
|
|
if (t.menu && t.menu.isMenuVisible)
|
|
return t.hideMenu();
|
|
|
|
if (!t.isMenuRendered) {
|
|
t.renderMenu();
|
|
t.isMenuRendered = true;
|
|
}
|
|
|
|
p1 = DOM.getPos(this.settings.menu_container);
|
|
p2 = DOM.getPos(e);
|
|
|
|
m = t.menu;
|
|
m.settings.offset_x = p2.x;
|
|
m.settings.offset_y = p2.y;
|
|
m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
|
|
|
|
// Select in menu
|
|
if (t.oldID)
|
|
m.items[t.oldID].setSelected(0);
|
|
|
|
each(t.items, function(o) {
|
|
if (o.value === t.selectedValue) {
|
|
m.items[o.id].setSelected(1);
|
|
t.oldID = o.id;
|
|
}
|
|
});
|
|
|
|
m.showMenu(0, e.clientHeight);
|
|
|
|
Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
|
|
DOM.addClass(t.id, t.classPrefix + 'Selected');
|
|
|
|
//DOM.get(t.id + '_text').focus();
|
|
},
|
|
|
|
hideMenu : function(e) {
|
|
var t = this;
|
|
|
|
// Prevent double toogles by canceling the mouse click event to the button
|
|
if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
|
|
return;
|
|
|
|
if (!e || !DOM.getParent(e.target, '.mceMenu')) {
|
|
DOM.removeClass(t.id, t.classPrefix + 'Selected');
|
|
Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
|
|
|
|
if (t.menu)
|
|
t.menu.hideMenu();
|
|
}
|
|
},
|
|
|
|
renderMenu : function() {
|
|
var t = this, m;
|
|
|
|
m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
|
|
menu_line : 1,
|
|
'class' : t.classPrefix + 'Menu mceNoIcons',
|
|
max_width : 150,
|
|
max_height : 150
|
|
});
|
|
|
|
m.onHideMenu.add(t.hideMenu, t);
|
|
|
|
m.add({
|
|
title : t.settings.title,
|
|
'class' : 'mceMenuItemTitle',
|
|
onclick : function() {
|
|
if (t.settings.onselect('') !== false)
|
|
t.select(''); // Must be runned after
|
|
}
|
|
});
|
|
|
|
each(t.items, function(o) {
|
|
o.id = DOM.uniqueId();
|
|
o.onclick = function() {
|
|
if (t.settings.onselect(o.value) !== false)
|
|
t.select(o.value); // Must be runned after
|
|
};
|
|
|
|
m.add(o);
|
|
});
|
|
|
|
t.onRenderMenu.dispatch(t, m);
|
|
t.menu = m;
|
|
},
|
|
|
|
postRender : function() {
|
|
var t = this, cp = t.classPrefix;
|
|
|
|
Event.add(t.id, 'click', t.showMenu, t);
|
|
Event.add(t.id + '_text', 'focus', function(e) {
|
|
if (!t._focused) {
|
|
t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
|
|
var idx = -1, v, kc = e.keyCode;
|
|
|
|
// Find current index
|
|
each(t.items, function(v, i) {
|
|
if (t.selectedValue == v.value)
|
|
idx = i;
|
|
});
|
|
|
|
// Move up/down
|
|
if (kc == 38)
|
|
v = t.items[idx - 1];
|
|
else if (kc == 40)
|
|
v = t.items[idx + 1];
|
|
else if (kc == 13) {
|
|
// Fake select on enter
|
|
v = t.selectedValue;
|
|
t.selectedValue = null; // Needs to be null to fake change
|
|
t.settings.onselect(v);
|
|
return Event.cancel(e);
|
|
}
|
|
|
|
if (v) {
|
|
t.hideMenu();
|
|
t.select(v.value);
|
|
}
|
|
});
|
|
}
|
|
|
|
t._focused = 1;
|
|
});
|
|
Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
|
|
|
|
// Old IE doesn't have hover on all elements
|
|
if (tinymce.isIE6 || !DOM.boxModel) {
|
|
Event.add(t.id, 'mouseover', function() {
|
|
if (!DOM.hasClass(t.id, cp + 'Disabled'))
|
|
DOM.addClass(t.id, cp + 'Hover');
|
|
});
|
|
|
|
Event.add(t.id, 'mouseout', function() {
|
|
if (!DOM.hasClass(t.id, cp + 'Disabled'))
|
|
DOM.removeClass(t.id, cp + 'Hover');
|
|
});
|
|
}
|
|
|
|
t.onPostRender.dispatch(t, DOM.get(t.id));
|
|
},
|
|
|
|
destroy : function() {
|
|
this.parent();
|
|
|
|
Event.clear(this.id + '_text');
|
|
Event.clear(this.id + '_open');
|
|
}
|
|
});
|
|
})(tinymce);(function(tinymce) {
|
|
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
|
|
|
|
tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
|
|
NativeListBox : function(id, s) {
|
|
this.parent(id, s);
|
|
this.classPrefix = 'mceNativeListBox';
|
|
},
|
|
|
|
setDisabled : function(s) {
|
|
DOM.get(this.id).disabled = s;
|
|
},
|
|
|
|
isDisabled : function() {
|
|
return DOM.get(this.id).disabled;
|
|
},
|
|
|
|
select : function(va) {
|
|
var t = this, fv, f;
|
|
|
|
if (va == undefined)
|
|
return t.selectByIndex(-1);
|
|
|
|
// Is string or number make function selector
|
|
if (va && va.call)
|
|
f = va;
|
|
else {
|
|
f = function(v) {
|
|
return v == va;
|
|
};
|
|
}
|
|
|
|
// Do we need to do something?
|
|
if (va != t.selectedValue) {
|
|
// Find item
|
|
each(t.items, function(o, i) {
|
|
if (f(o.value)) {
|
|
fv = 1;
|
|
t.selectByIndex(i);
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if (!fv)
|
|
t.selectByIndex(-1);
|
|
}
|
|
},
|
|
|
|
selectByIndex : function(idx) {
|
|
DOM.get(this.id).selectedIndex = idx + 1;
|
|
this.selectedValue = this.items[idx] ? this.items[idx].value : null;
|
|
},
|
|
|
|
add : function(n, v, a) {
|
|
var o, t = this;
|
|
|
|
a = a || {};
|
|
a.value = v;
|
|
|
|
if (t.isRendered())
|
|
DOM.add(DOM.get(this.id), 'option', a, n);
|
|
|
|
o = {
|
|
title : n,
|
|
value : v,
|
|
attribs : a
|
|
};
|
|
|
|
t.items.push(o);
|
|
t.onAdd.dispatch(t, o);
|
|
},
|
|
|
|
getLength : function() {
|
|
return DOM.get(this.id).options.length - 1;
|
|
},
|
|
|
|
renderHTML : function() {
|
|
var h, t = this;
|
|
|
|
h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
|
|
|
|
each(t.items, function(it) {
|
|
h += DOM.createHTML('option', {value : it.value}, it.title);
|
|
});
|
|
|
|
h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
|
|
|
|
return h;
|
|
},
|
|
|
|
postRender : function() {
|
|
var t = this, ch;
|
|
|
|
t.rendered = true;
|
|
|
|
function onChange(e) {
|
|
var v = t.items[e.target.selectedIndex - 1];
|
|
|
|
if (v && (v = v.value)) {
|
|
t.onChange.dispatch(t, v);
|
|
|
|
if (t.settings.onselect)
|
|
t.settings.onselect(v);
|
|
}
|
|
};
|
|
|
|
Event.add(t.id, 'change', onChange);
|
|
|
|
// Accessibility keyhandler
|
|
Event.add(t.id, 'keydown', function(e) {
|
|
var bf;
|
|
|
|
Event.remove(t.id, 'change', ch);
|
|
|
|
bf = Event.add(t.id, 'blur', function() {
|
|
Event.add(t.id, 'change', onChange);
|
|
Event.remove(t.id, 'blur', bf);
|
|
});
|
|
|
|
if (e.keyCode == 13 || e.keyCode == 32) {
|
|
onChange(e);
|
|
return Event.cancel(e);
|
|
}
|
|
});
|
|
|
|
t.onPostRender.dispatch(t, DOM.get(t.id));
|
|
}
|
|
});
|
|
})(tinymce);(function(tinymce) {
|
|
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
|
|
|
|
tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
|
|
MenuButton : function(id, s) {
|
|
this.parent(id, s);
|
|
|
|
this.onRenderMenu = new tinymce.util.Dispatcher(this);
|
|
|
|
s.menu_container = s.menu_container || DOM.doc.body;
|
|
},
|
|
|
|
showMenu : function() {
|
|
var t = this, p1, p2, e = DOM.get(t.id), m;
|
|
|
|
if (t.isDisabled())
|
|
return;
|
|
|
|
if (!t.isMenuRendered) {
|
|
t.renderMenu();
|
|
t.isMenuRendered = true;
|
|
}
|
|
|
|
if (t.isMenuVisible)
|
|
return t.hideMenu();
|
|
|
|
p1 = DOM.getPos(t.settings.menu_container);
|
|
p2 = DOM.getPos(e);
|
|
|
|
m = t.menu;
|
|
m.settings.offset_x = p2.x;
|
|
m.settings.offset_y = p2.y;
|
|
m.settings.vp_offset_x = p2.x;
|
|
m.settings.vp_offset_y = p2.y;
|
|
m.settings.keyboard_focus = t._focused;
|
|
m.showMenu(0, e.clientHeight);
|
|
|
|
Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
|
|
t.setState('Selected', 1);
|
|
|
|
t.isMenuVisible = 1;
|
|
},
|
|
|
|
renderMenu : function() {
|
|
var t = this, m;
|
|
|
|
m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
|
|
menu_line : 1,
|
|
'class' : this.classPrefix + 'Menu',
|
|
icons : t.settings.icons
|
|
});
|
|
|
|
m.onHideMenu.add(t.hideMenu, t);
|
|
|
|
t.onRenderMenu.dispatch(t, m);
|
|
t.menu = m;
|
|
},
|
|
|
|
hideMenu : function(e) {
|
|
var t = this;
|
|
|
|
// Prevent double toogles by canceling the mouse click event to the button
|
|
if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
|
|
return;
|
|
|
|
if (!e || !DOM.getParent(e.target, '.mceMenu')) {
|
|
t.setState('Selected', 0);
|
|
Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
|
|
if (t.menu)
|
|
t.menu.hideMenu();
|
|
}
|
|
|
|
t.isMenuVisible = 0;
|
|
},
|
|
|
|
postRender : function() {
|
|
var t = this, s = t.settings;
|
|
|
|
Event.add(t.id, 'click', function() {
|
|
if (!t.isDisabled()) {
|
|
if (s.onclick)
|
|
s.onclick(t.value);
|
|
|
|
t.showMenu();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
|
|
|
|
tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
|
|
SplitButton : function(id, s) {
|
|
this.parent(id, s);
|
|
this.classPrefix = 'mceSplitButton';
|
|
},
|
|
|
|
renderHTML : function() {
|
|
var h, t = this, s = t.settings, h1;
|
|
|
|
h = '<tbody><tr>';
|
|
|
|
if (s.image)
|
|
h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
|
|
else {
|
|
var titleDiv = DOM.createHTML("div", {
|
|
"style": "clear: none; width: 1px; height: 1px; filter: alpha(opacity=0.01); color: transparent; overflow: hidden;"
|
|
}, s.title);
|
|
h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, titleDiv);
|
|
}
|
|
|
|
h += '<td>' + DOM.createHTML('a', {id : t.id + '_action', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
|
|
|
|
h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
|
|
|
|
var titleDiv = DOM.createHTML("div", {
|
|
"style": "clear: none; width: 1px; height: 1px; filter: alpha(opacity=0.01); color: transparent; overflow: hidden;"
|
|
}, h1);
|
|
|
|
h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, titleDiv) + '</td>';
|
|
|
|
h += '</tr></tbody>';
|
|
|
|
return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
|
|
},
|
|
|
|
postRender : function() {
|
|
var t = this, s = t.settings;
|
|
|
|
if (s.onclick) {
|
|
Event.add(t.id + '_action', 'click', function() {
|
|
if (!t.isDisabled())
|
|
s.onclick(t.value);
|
|
});
|
|
}
|
|
|
|
Event.add(t.id + '_open', 'click', t.showMenu, t);
|
|
Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
|
|
Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
|
|
|
|
// Old IE doesn't have hover on all elements
|
|
if (tinymce.isIE6 || !DOM.boxModel) {
|
|
Event.add(t.id, 'mouseover', function() {
|
|
if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
|
|
DOM.addClass(t.id, 'mceSplitButtonHover');
|
|
});
|
|
|
|
Event.add(t.id, 'mouseout', function() {
|
|
if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
|
|
DOM.removeClass(t.id, 'mceSplitButtonHover');
|
|
});
|
|
}
|
|
},
|
|
|
|
destroy : function() {
|
|
this.parent();
|
|
|
|
Event.clear(this.id + '_action');
|
|
Event.clear(this.id + '_open');
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
|
|
|
|
tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
|
|
ColorSplitButton : function(id, s) {
|
|
var t = this;
|
|
|
|
t.parent(id, s);
|
|
|
|
t.settings = s = tinymce.extend({
|
|
colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
|
|
grid_width : 8,
|
|
default_color : '#888888'
|
|
}, t.settings);
|
|
|
|
t.onShowMenu = new tinymce.util.Dispatcher(t);
|
|
|
|
t.onHideMenu = new tinymce.util.Dispatcher(t);
|
|
|
|
t.value = s.default_color;
|
|
},
|
|
|
|
showMenu : function() {
|
|
var t = this, r, p, e, p2;
|
|
|
|
if (t.isDisabled())
|
|
return;
|
|
|
|
if (!t.isMenuRendered) {
|
|
t.renderMenu();
|
|
t.isMenuRendered = true;
|
|
}
|
|
|
|
if (t.isMenuVisible)
|
|
return t.hideMenu();
|
|
|
|
e = DOM.get(t.id);
|
|
DOM.show(t.id + '_menu');
|
|
DOM.addClass(e, 'mceSplitButtonSelected');
|
|
p2 = DOM.getPos(e);
|
|
DOM.setStyles(t.id + '_menu', {
|
|
left : p2.x,
|
|
top : p2.y + e.clientHeight,
|
|
zIndex : 200000
|
|
});
|
|
e = 0;
|
|
|
|
Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
|
|
t.onShowMenu.dispatch(t);
|
|
|
|
if (t._focused) {
|
|
t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
|
|
if (e.keyCode == 27)
|
|
t.hideMenu();
|
|
});
|
|
|
|
DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
|
|
}
|
|
|
|
t.isMenuVisible = 1;
|
|
},
|
|
|
|
hideMenu : function(e) {
|
|
var t = this;
|
|
|
|
// Prevent double toogles by canceling the mouse click event to the button
|
|
if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
|
|
return;
|
|
|
|
if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
|
|
DOM.removeClass(t.id, 'mceSplitButtonSelected');
|
|
Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
|
|
Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
|
|
DOM.hide(t.id + '_menu');
|
|
}
|
|
|
|
t.onHideMenu.dispatch(t);
|
|
|
|
t.isMenuVisible = 0;
|
|
},
|
|
|
|
renderMenu : function() {
|
|
var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
|
|
|
|
w = DOM.add(s.menu_container, 'div', {id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
|
|
m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
|
|
DOM.add(m, 'span', {'class' : 'mceMenuLine'});
|
|
|
|
n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
|
|
tb = DOM.add(n, 'tbody');
|
|
|
|
// Generate color grid
|
|
i = 0;
|
|
each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
|
|
c = c.replace(/^#/, '');
|
|
|
|
if (!i--) {
|
|
tr = DOM.add(tb, 'tr');
|
|
i = s.grid_width - 1;
|
|
}
|
|
|
|
n = DOM.add(tr, 'td');
|
|
|
|
n = DOM.add(n, 'a', {
|
|
href : 'javascript:;',
|
|
style : {
|
|
backgroundColor : '#' + c
|
|
},
|
|
mce_color : '#' + c
|
|
});
|
|
});
|
|
|
|
if (s.more_colors_func) {
|
|
n = DOM.add(tb, 'tr');
|
|
n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
|
|
n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
|
|
|
|
Event.add(n, 'click', function(e) {
|
|
s.more_colors_func.call(s.more_colors_scope || this);
|
|
return Event.cancel(e); // Cancel to fix onbeforeunload problem
|
|
});
|
|
}
|
|
|
|
DOM.addClass(m, 'mceColorSplitMenu');
|
|
|
|
Event.add(t.id + '_menu', 'click', function(e) {
|
|
var c;
|
|
|
|
e = e.target;
|
|
|
|
if (e.nodeName == 'A' && (c = e.getAttribute('mce_color')))
|
|
t.setColor(c);
|
|
|
|
return Event.cancel(e); // Prevent IE auto save warning
|
|
});
|
|
|
|
return w;
|
|
},
|
|
|
|
setColor : function(c) {
|
|
var t = this;
|
|
|
|
DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
|
|
|
|
t.value = c;
|
|
t.hideMenu();
|
|
t.settings.onselect(c);
|
|
},
|
|
|
|
postRender : function() {
|
|
var t = this, id = t.id;
|
|
|
|
t.parent();
|
|
DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
|
|
DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
|
|
},
|
|
|
|
destroy : function() {
|
|
this.parent();
|
|
|
|
Event.clear(this.id + '_menu');
|
|
Event.clear(this.id + '_more');
|
|
DOM.remove(this.id + '_menu');
|
|
}
|
|
});
|
|
})(tinymce);
|
|
tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
|
|
renderHTML : function() {
|
|
var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
|
|
|
|
cl = t.controls;
|
|
for (i=0; i<cl.length; i++) {
|
|
// Get current control, prev control, next control and if the control is a list box or not
|
|
co = cl[i];
|
|
pr = cl[i - 1];
|
|
nx = cl[i + 1];
|
|
|
|
// Add toolbar start
|
|
if (i === 0) {
|
|
c = 'mceToolbarStart';
|
|
|
|
if (co.Button)
|
|
c += ' mceToolbarStartButton';
|
|
else if (co.SplitButton)
|
|
c += ' mceToolbarStartSplitButton';
|
|
else if (co.ListBox)
|
|
c += ' mceToolbarStartListBox';
|
|
|
|
h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
|
|
}
|
|
|
|
// Add toolbar end before list box and after the previous button
|
|
// This is to fix the o2k7 editor skins
|
|
if (pr && co.ListBox) {
|
|
if (pr.Button || pr.SplitButton)
|
|
h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
|
|
}
|
|
|
|
// Render control HTML
|
|
|
|
// IE 8 quick fix, needed to propertly generate a hit area for anchors
|
|
if (dom.stdMode)
|
|
h += '<td style="position: relative">' + co.renderHTML() + '</td>';
|
|
else
|
|
h += '<td>' + co.renderHTML() + '</td>';
|
|
|
|
// Add toolbar start after list box and before the next button
|
|
// This is to fix the o2k7 editor skins
|
|
if (nx && co.ListBox) {
|
|
if (nx.Button || nx.SplitButton)
|
|
h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
|
|
}
|
|
}
|
|
|
|
c = 'mceToolbarEnd';
|
|
|
|
if (co.Button)
|
|
c += ' mceToolbarEndButton';
|
|
else if (co.SplitButton)
|
|
c += ' mceToolbarEndSplitButton';
|
|
else if (co.ListBox)
|
|
c += ' mceToolbarEndListBox';
|
|
|
|
h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
|
|
|
|
return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '<tbody><tr>' + h + '</tr></tbody>');
|
|
}
|
|
});
|
|
(function(tinymce) {
|
|
var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
|
|
|
|
tinymce.create('tinymce.AddOnManager', {
|
|
items : [],
|
|
urls : {},
|
|
lookup : {},
|
|
|
|
onAdd : new Dispatcher(this),
|
|
|
|
get : function(n) {
|
|
return this.lookup[n];
|
|
},
|
|
|
|
requireLangPack : function(n) {
|
|
var u, s = tinymce.EditorManager.settings;
|
|
|
|
if (s && s.language) {
|
|
u = this.urls[n] + '/langs/' + s.language + '.js';
|
|
|
|
if (!tinymce.dom.Event.domLoaded && !s.strict_mode)
|
|
tinymce.ScriptLoader.load(u);
|
|
else
|
|
tinymce.ScriptLoader.add(u);
|
|
}
|
|
},
|
|
|
|
add : function(id, o) {
|
|
this.items.push(o);
|
|
this.lookup[id] = o;
|
|
this.onAdd.dispatch(this, id, o);
|
|
|
|
return o;
|
|
},
|
|
|
|
load : function(n, u, cb, s) {
|
|
var t = this;
|
|
|
|
if (t.urls[n])
|
|
return;
|
|
|
|
if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
|
|
u = tinymce.baseURL + '/' + u;
|
|
|
|
t.urls[n] = u.substring(0, u.lastIndexOf('/'));
|
|
tinymce.ScriptLoader.add(u, cb, s);
|
|
}
|
|
});
|
|
|
|
// Create plugin and theme managers
|
|
tinymce.PluginManager = new tinymce.AddOnManager();
|
|
tinymce.ThemeManager = new tinymce.AddOnManager();
|
|
}(tinymce));
|
|
|
|
(function(tinymce) {
|
|
// Shorten names
|
|
var each = tinymce.each, extend = tinymce.extend, DOM = tinymce.DOM, Event = tinymce.dom.Event, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, explode = tinymce.explode;
|
|
|
|
tinymce.create('static tinymce.EditorManager', {
|
|
editors : {},
|
|
|
|
i18n : {},
|
|
|
|
activeEditor : null,
|
|
|
|
preInit : function() {
|
|
var t = this, lo = window.location;
|
|
|
|
// Setup some URLs where the editor API is located and where the document is
|
|
tinymce.documentBaseURL = lo.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
|
|
if (!/[\/\\]$/.test(tinymce.documentBaseURL))
|
|
tinymce.documentBaseURL += '/';
|
|
|
|
tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
|
|
tinymce.EditorManager.baseURI = new tinymce.util.URI(tinymce.baseURL);
|
|
|
|
// Add before unload listener
|
|
// This was required since IE was leaking memory if you added and removed beforeunload listeners
|
|
// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
|
|
t.onBeforeUnload = new tinymce.util.Dispatcher(t);
|
|
|
|
// Must be on window or IE will leak if the editor is placed in frame or iframe
|
|
Event.add(window, 'beforeunload', function(e) {
|
|
t.onBeforeUnload.dispatch(t, e);
|
|
});
|
|
},
|
|
|
|
init : function(s) {
|
|
var t = this, pl, sl = tinymce.ScriptLoader, c, e, el = [], ed;
|
|
|
|
function execCallback(se, n, s) {
|
|
var f = se[n];
|
|
|
|
if (!f)
|
|
return;
|
|
|
|
if (tinymce.is(f, 'string')) {
|
|
s = f.replace(/\.\w+$/, '');
|
|
s = s ? tinymce.resolve(s) : 0;
|
|
f = tinymce.resolve(f);
|
|
}
|
|
|
|
return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
|
|
};
|
|
|
|
s = extend({
|
|
theme : "simple",
|
|
language : "en",
|
|
strict_loading_mode : document.contentType == 'application/xhtml+xml'
|
|
}, s);
|
|
|
|
t.settings = s;
|
|
|
|
// If page not loaded and strict mode isn't enabled then load them
|
|
if (!Event.domLoaded && !s.strict_loading_mode) {
|
|
// Load language
|
|
if (s.language)
|
|
sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
|
|
|
|
// Load theme
|
|
if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
|
|
ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
|
|
|
|
// Load plugins
|
|
if (s.plugins) {
|
|
pl = explode(s.plugins);
|
|
|
|
// Load rest if plugins
|
|
each(pl, function(v) {
|
|
if (v && v.charAt(0) != '-' && !PluginManager.urls[v]) {
|
|
// Skip safari plugin for other browsers
|
|
if (!tinymce.isWebKit && v == 'safari')
|
|
return;
|
|
|
|
PluginManager.load(v, 'plugins/' + v + '/editor_plugin' + tinymce.suffix + '.js');
|
|
}
|
|
});
|
|
}
|
|
|
|
sl.loadQueue();
|
|
}
|
|
|
|
// Legacy call
|
|
Event.add(document, 'init', function() {
|
|
var l, co;
|
|
|
|
execCallback(s, 'onpageload');
|
|
|
|
// Verify that it's a valid browser
|
|
if (s.browsers) {
|
|
l = false;
|
|
|
|
each(explode(s.browsers), function(v) {
|
|
switch (v) {
|
|
case 'ie':
|
|
case 'msie':
|
|
if (tinymce.isIE)
|
|
l = true;
|
|
break;
|
|
|
|
case 'gecko':
|
|
if (tinymce.isGecko)
|
|
l = true;
|
|
break;
|
|
|
|
case 'safari':
|
|
case 'webkit':
|
|
if (tinymce.isWebKit)
|
|
l = true;
|
|
break;
|
|
|
|
case 'opera':
|
|
if (tinymce.isOpera)
|
|
l = true;
|
|
|
|
break;
|
|
}
|
|
});
|
|
|
|
// Not a valid one
|
|
if (!l)
|
|
return;
|
|
}
|
|
|
|
switch (s.mode) {
|
|
case "exact":
|
|
l = s.elements || '';
|
|
|
|
if(l.length > 0) {
|
|
each(explode(l), function(v) {
|
|
if (DOM.get(v)) {
|
|
ed = new tinymce.Editor(v, s);
|
|
el.push(ed);
|
|
ed.render(1);
|
|
} else {
|
|
c = 0;
|
|
|
|
each(document.forms, function(f) {
|
|
each(f.elements, function(e) {
|
|
if (e.name === v) {
|
|
v = 'mce_editor_' + c;
|
|
DOM.setAttrib(e, 'id', v);
|
|
|
|
ed = new tinymce.Editor(v, s);
|
|
el.push(ed);
|
|
ed.render(1);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
|
|
case "textareas":
|
|
case "specific_textareas":
|
|
function hasClass(n, c) {
|
|
return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
|
|
};
|
|
|
|
each(DOM.select('textarea'), function(v) {
|
|
if (s.editor_deselector && hasClass(v, s.editor_deselector))
|
|
return;
|
|
|
|
if (!s.editor_selector || hasClass(v, s.editor_selector)) {
|
|
// Can we use the name
|
|
e = DOM.get(v.name);
|
|
if (!v.id && !e)
|
|
v.id = v.name;
|
|
|
|
// Generate unique name if missing or already exists
|
|
if (!v.id || t.get(v.id))
|
|
v.id = DOM.uniqueId();
|
|
|
|
ed = new tinymce.Editor(v.id, s);
|
|
el.push(ed);
|
|
ed.render(1);
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
|
|
// Call onInit when all editors are initialized
|
|
if (s.oninit) {
|
|
l = co = 0;
|
|
|
|
each (el, function(ed) {
|
|
co++;
|
|
|
|
if (!ed.initialized) {
|
|
// Wait for it
|
|
ed.onInit.add(function() {
|
|
l++;
|
|
|
|
// All done
|
|
if (l == co)
|
|
execCallback(s, 'oninit');
|
|
});
|
|
} else
|
|
l++;
|
|
|
|
// All done
|
|
if (l == co)
|
|
execCallback(s, 'oninit');
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
get : function(id) {
|
|
return this.editors[id];
|
|
},
|
|
|
|
getInstanceById : function(id) {
|
|
return this.get(id);
|
|
},
|
|
|
|
add : function(e) {
|
|
this.editors[e.id] = e;
|
|
this._setActive(e);
|
|
|
|
return e;
|
|
},
|
|
|
|
remove : function(e) {
|
|
var t = this;
|
|
|
|
// Not in the collection
|
|
if (!t.editors[e.id])
|
|
return null;
|
|
|
|
delete t.editors[e.id];
|
|
|
|
// Select another editor since the active one was removed
|
|
if (t.activeEditor == e) {
|
|
t._setActive(null);
|
|
|
|
each(t.editors, function(e) {
|
|
t._setActive(e);
|
|
return false; // Break
|
|
});
|
|
}
|
|
|
|
e.destroy();
|
|
|
|
return e;
|
|
},
|
|
|
|
execCommand : function(c, u, v) {
|
|
var t = this, ed = t.get(v), w;
|
|
|
|
// Manager commands
|
|
switch (c) {
|
|
case "mceFocus":
|
|
ed.focus();
|
|
return true;
|
|
|
|
case "mceAddEditor":
|
|
case "mceAddControl":
|
|
if (!t.get(v))
|
|
new tinymce.Editor(v, t.settings).render();
|
|
|
|
return true;
|
|
|
|
case "mceAddFrameControl":
|
|
w = v.window;
|
|
|
|
// Add tinyMCE global instance and tinymce namespace to specified window
|
|
w.tinyMCE = tinyMCE;
|
|
w.tinymce = tinymce;
|
|
|
|
tinymce.DOM.doc = w.document;
|
|
tinymce.DOM.win = w;
|
|
|
|
ed = new tinymce.Editor(v.element_id, v);
|
|
ed.render();
|
|
|
|
// Fix IE memory leaks
|
|
if (tinymce.isIE) {
|
|
function clr() {
|
|
ed.destroy();
|
|
w.detachEvent('onunload', clr);
|
|
w = w.tinyMCE = w.tinymce = null; // IE leak
|
|
};
|
|
|
|
w.attachEvent('onunload', clr);
|
|
}
|
|
|
|
v.page_window = null;
|
|
|
|
return true;
|
|
|
|
case "mceRemoveEditor":
|
|
case "mceRemoveControl":
|
|
if (ed)
|
|
ed.remove();
|
|
|
|
return true;
|
|
|
|
case 'mceToggleEditor':
|
|
if (!ed) {
|
|
t.execCommand('mceAddControl', 0, v);
|
|
return true;
|
|
}
|
|
|
|
if (ed.isHidden())
|
|
ed.show();
|
|
else
|
|
ed.hide();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Run command on active editor
|
|
if (t.activeEditor)
|
|
return t.activeEditor.execCommand(c, u, v);
|
|
|
|
return false;
|
|
},
|
|
|
|
execInstanceCommand : function(id, c, u, v) {
|
|
var ed = this.get(id);
|
|
|
|
if (ed)
|
|
return ed.execCommand(c, u, v);
|
|
|
|
return false;
|
|
},
|
|
|
|
triggerSave : function() {
|
|
each(this.editors, function(e) {
|
|
e.save();
|
|
});
|
|
},
|
|
|
|
addI18n : function(p, o) {
|
|
var lo, i18n = this.i18n;
|
|
|
|
if (!tinymce.is(p, 'string')) {
|
|
each(p, function(o, lc) {
|
|
each(o, function(o, g) {
|
|
each(o, function(o, k) {
|
|
if (g === 'common')
|
|
i18n[lc + '.' + k] = o;
|
|
else
|
|
i18n[lc + '.' + g + '.' + k] = o;
|
|
});
|
|
});
|
|
});
|
|
} else {
|
|
each(o, function(o, k) {
|
|
i18n[p + '.' + k] = o;
|
|
});
|
|
}
|
|
},
|
|
|
|
// Private methods
|
|
|
|
_setActive : function(e) {
|
|
this.selectedInstance = this.activeEditor = e;
|
|
}
|
|
});
|
|
|
|
tinymce.EditorManager.preInit();
|
|
})(tinymce);
|
|
|
|
var tinyMCE = window.tinyMCE = tinymce.EditorManager;
|
|
(function(tinymce) {
|
|
var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, Dispatcher = tinymce.util.Dispatcher;
|
|
var each = tinymce.each, isGecko = tinymce.isGecko, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit;
|
|
var is = tinymce.is, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, EditorManager = tinymce.EditorManager;
|
|
var inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
|
|
|
|
tinymce.create('tinymce.Editor', {
|
|
Editor : function(id, s) {
|
|
var t = this;
|
|
|
|
t.id = t.editorId = id;
|
|
|
|
t.execCommands = {};
|
|
t.queryStateCommands = {};
|
|
t.queryValueCommands = {};
|
|
|
|
t.isNotDirty = false;
|
|
|
|
t.plugins = {};
|
|
|
|
// Add events to the editor
|
|
each([
|
|
'onPreInit',
|
|
|
|
'onBeforeRenderUI',
|
|
|
|
'onPostRender',
|
|
|
|
'onInit',
|
|
|
|
'onRemove',
|
|
|
|
'onActivate',
|
|
|
|
'onDeactivate',
|
|
|
|
'onClick',
|
|
|
|
'onEvent',
|
|
|
|
'onMouseUp',
|
|
|
|
'onMouseDown',
|
|
|
|
'onDblClick',
|
|
|
|
'onKeyDown',
|
|
|
|
'onKeyUp',
|
|
|
|
'onKeyPress',
|
|
|
|
'onContextMenu',
|
|
|
|
'onSubmit',
|
|
|
|
'onReset',
|
|
|
|
'onPaste',
|
|
|
|
'onPreProcess',
|
|
|
|
'onPostProcess',
|
|
|
|
'onBeforeSetContent',
|
|
|
|
'onBeforeGetContent',
|
|
|
|
'onSetContent',
|
|
|
|
'onGetContent',
|
|
|
|
'onLoadContent',
|
|
|
|
'onSaveContent',
|
|
|
|
'onNodeChange',
|
|
|
|
'onChange',
|
|
|
|
'onBeforeExecCommand',
|
|
|
|
'onExecCommand',
|
|
|
|
'onUndo',
|
|
|
|
'onRedo',
|
|
|
|
'onVisualAid',
|
|
|
|
'onSetProgressState'
|
|
], function(e) {
|
|
t[e] = new Dispatcher(t);
|
|
});
|
|
|
|
t.settings = s = extend({
|
|
id : id,
|
|
language : 'en',
|
|
docs_language : 'en',
|
|
theme : 'simple',
|
|
skin : 'default',
|
|
delta_width : 0,
|
|
delta_height : 0,
|
|
popup_css : '',
|
|
plugins : '',
|
|
document_base_url : tinymce.documentBaseURL,
|
|
add_form_submit_trigger : 1,
|
|
submit_patch : 1,
|
|
add_unload_trigger : 1,
|
|
convert_urls : 1,
|
|
relative_urls : 1,
|
|
remove_script_host : 1,
|
|
table_inline_editing : 0,
|
|
object_resizing : 1,
|
|
cleanup : 1,
|
|
accessibility_focus : 1,
|
|
custom_shortcuts : 1,
|
|
custom_undo_redo_keyboard_shortcuts : 1,
|
|
custom_undo_redo_restore_selection : 1,
|
|
custom_undo_redo : 1,
|
|
doctype : '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">',
|
|
visual_table_class : 'mceItemTable',
|
|
visual : 1,
|
|
inline_styles : true,
|
|
convert_fonts_to_spans : true,
|
|
font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
|
|
apply_source_formatting : 0,
|
|
directionality : 'ltr',
|
|
forced_root_block : 'p',
|
|
valid_elements : '@[id|class|style|title|dir<ltr?rtl|lang|xml::lang|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],a[rel|rev|charset|hreflang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur],strong/b,em/i,strike,u,#p,-ol[type|compact],-ul[type|compact],-li,br,img[longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align],-sub,-sup,-blockquote[cite],-table[border|cellspacing|cellpadding|width|frame|rules|height|align|summary|bgcolor|background|bordercolor],-tr[rowspan|width|height|align|valign|bgcolor|background|bordercolor],tbody,thead,tfoot,#td[colspan|rowspan|width|height|align|valign|bgcolor|background|bordercolor|scope],#th[colspan|rowspan|width|height|align|valign|scope],caption,-div,-span,-code,-pre,address,-h1,-h2,-h3,-h4,-h5,-h6,hr[size|noshade],-font[face|size|color],dd,dl,dt,cite,abbr,acronym,del[datetime|cite],ins[datetime|cite],object[classid|width|height|codebase|*],param[name|value],embed[type|width|height|src|*],script[src|type],map[name],area[shape|coords|href|alt|target],bdo,button,col[align|char|charoff|span|valign|width],colgroup[align|char|charoff|span|valign|width],dfn,fieldset,form[action|accept|accept-charset|enctype|method],input[accept|alt|checked|disabled|maxlength|name|readonly|size|src|type|value|tabindex|accesskey],kbd,label[for],legend,noscript,optgroup[label|disabled],option[disabled|label|selected|value],q[cite],samp,select[disabled|multiple|name|size],small,textarea[cols|rows|disabled|name|readonly],tt,var,big',
|
|
hidden_input : 1,
|
|
padd_empty_editor : 1,
|
|
render_ui : 1,
|
|
init_theme : 1,
|
|
force_p_newlines : 1,
|
|
indentation : '30px',
|
|
keep_styles : 1,
|
|
fix_table_elements : 1,
|
|
removeformat_selector : 'span,b,strong,em,i,font,u,strike'
|
|
}, s);
|
|
|
|
t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
|
|
base_uri : tinyMCE.baseURI
|
|
});
|
|
|
|
t.baseURI = EditorManager.baseURI;
|
|
|
|
// Call setup
|
|
t.execCallback('setup', t);
|
|
},
|
|
|
|
render : function(nst) {
|
|
var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
|
|
|
|
// Page is not loaded yet, wait for it
|
|
if (!Event.domLoaded) {
|
|
Event.add(document, 'init', function() {
|
|
t.render();
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Force strict loading mode if render us called by user and not internally
|
|
if (!nst) {
|
|
s.strict_loading_mode = 1;
|
|
tinyMCE.settings = s;
|
|
}
|
|
|
|
// Element not found, then skip initialization
|
|
if (!t.getElement())
|
|
return;
|
|
|
|
if (s.strict_loading_mode) {
|
|
sl.settings.strict_mode = s.strict_loading_mode;
|
|
tinymce.DOM.settings.strict = 1;
|
|
}
|
|
|
|
// Add hidden input for non input elements inside form elements
|
|
if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
|
|
DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
|
|
|
|
if (tinymce.WindowManager)
|
|
t.windowManager = new tinymce.WindowManager(t);
|
|
|
|
if (s.encoding == 'xml') {
|
|
t.onGetContent.add(function(ed, o) {
|
|
if (o.save)
|
|
o.content = DOM.encode(o.content);
|
|
});
|
|
}
|
|
|
|
if (s.add_form_submit_trigger) {
|
|
t.onSubmit.addToTop(function() {
|
|
if (t.initialized) {
|
|
t.save();
|
|
t.isNotDirty = 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (s.add_unload_trigger) {
|
|
t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
|
|
if (t.initialized && !t.destroyed && !t.isHidden())
|
|
t.save({format : 'raw', no_events : true});
|
|
});
|
|
}
|
|
|
|
tinymce.addUnload(t.destroy, t);
|
|
|
|
if (s.submit_patch) {
|
|
t.onBeforeRenderUI.add(function() {
|
|
var n = t.getElement().form;
|
|
|
|
if (!n)
|
|
return;
|
|
|
|
// Already patched
|
|
if (n._mceOldSubmit)
|
|
return;
|
|
|
|
// Check page uses id="submit" or name="submit" for it's submit button
|
|
if (!n.submit.nodeType && !n.submit.length) {
|
|
t.formElement = n;
|
|
n._mceOldSubmit = n.submit;
|
|
n.submit = function() {
|
|
// Save all instances
|
|
EditorManager.triggerSave();
|
|
t.isNotDirty = 1;
|
|
|
|
return t.formElement._mceOldSubmit(t.formElement);
|
|
};
|
|
}
|
|
|
|
n = null;
|
|
});
|
|
}
|
|
|
|
// Load scripts
|
|
function loadScripts() {
|
|
if (s.language)
|
|
sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
|
|
|
|
if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
|
|
ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
|
|
|
|
each(explode(s.plugins), function(p) {
|
|
if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
|
|
// Skip safari plugin for other browsers
|
|
if (!isWebKit && p == 'safari')
|
|
return;
|
|
|
|
PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
|
|
}
|
|
});
|
|
|
|
// Init when que is loaded
|
|
sl.loadQueue(function() {
|
|
if (!t.removed)
|
|
t.init();
|
|
});
|
|
};
|
|
|
|
loadScripts();
|
|
},
|
|
|
|
init : function() {
|
|
var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
|
|
|
|
EditorManager.add(t);
|
|
|
|
if (s.theme) {
|
|
s.theme = s.theme.replace(/-/, '');
|
|
o = ThemeManager.get(s.theme);
|
|
t.theme = new o();
|
|
|
|
if (t.theme.init && s.init_theme)
|
|
t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
|
|
}
|
|
|
|
// Create all plugins
|
|
each(explode(s.plugins.replace(/\-/g, '')), function(p) {
|
|
var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
|
|
|
|
if (c) {
|
|
po = new c(t, u);
|
|
|
|
t.plugins[p] = po;
|
|
|
|
if (po.init)
|
|
po.init(t, u);
|
|
}
|
|
});
|
|
|
|
// Setup popup CSS path(s)
|
|
if (s.popup_css !== false) {
|
|
if (s.popup_css)
|
|
s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
|
|
else
|
|
s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
|
|
}
|
|
|
|
if (s.popup_css_add)
|
|
s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
|
|
|
|
t.controlManager = new tinymce.ControlManager(t);
|
|
|
|
t.undoManager = new tinymce.UndoManager(t);
|
|
|
|
// Pass through
|
|
t.undoManager.onAdd.add(function(um, l) {
|
|
if (!l.initial)
|
|
return t.onChange.dispatch(t, l, um);
|
|
});
|
|
|
|
t.undoManager.onUndo.add(function(um, l) {
|
|
return t.onUndo.dispatch(t, l, um);
|
|
});
|
|
|
|
t.undoManager.onRedo.add(function(um, l) {
|
|
return t.onRedo.dispatch(t, l, um);
|
|
});
|
|
|
|
if (s.custom_undo_redo) {
|
|
t.onExecCommand.add(function(ed, cmd, ui, val, a) {
|
|
if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
|
|
t.undoManager.add();
|
|
});
|
|
}
|
|
|
|
t.onExecCommand.add(function(ed, c) {
|
|
// Don't refresh the select lists until caret move
|
|
if (!/^(FontName|FontSize)$/.test(c))
|
|
t.nodeChanged();
|
|
});
|
|
|
|
// Remove ghost selections on images and tables in Gecko
|
|
if (isGecko) {
|
|
function repaint(a, o) {
|
|
if (!o || !o.initial)
|
|
t.execCommand('mceRepaint');
|
|
};
|
|
|
|
t.onUndo.add(repaint);
|
|
t.onRedo.add(repaint);
|
|
t.onSetContent.add(repaint);
|
|
}
|
|
|
|
// Enables users to override the control factory
|
|
t.onBeforeRenderUI.dispatch(t, t.controlManager);
|
|
|
|
// Measure box
|
|
if (s.render_ui) {
|
|
w = s.width || e.style.width || e.offsetWidth;
|
|
h = s.height || e.style.height || e.offsetHeight;
|
|
t.orgDisplay = e.style.display;
|
|
re = /^[0-9\.]+(|px)$/i;
|
|
|
|
if (re.test('' + w))
|
|
w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
|
|
|
|
if (re.test('' + h))
|
|
h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
|
|
|
|
// Render UI
|
|
o = t.theme.renderUI({
|
|
targetNode : e,
|
|
width : w,
|
|
height : h,
|
|
deltaWidth : s.delta_width,
|
|
deltaHeight : s.delta_height
|
|
});
|
|
|
|
t.editorContainer = o.editorContainer;
|
|
}
|
|
|
|
|
|
// User specified a document.domain value
|
|
if (document.domain && location.hostname != document.domain)
|
|
tinymce.relaxedDomain = document.domain;
|
|
|
|
// Resize editor
|
|
DOM.setStyles(o.sizeContainer || o.editorContainer, {
|
|
width : w,
|
|
height : h
|
|
});
|
|
|
|
h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
|
|
if (h < 100)
|
|
h = 100;
|
|
|
|
t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
|
|
|
|
// We only need to override paths if we have to
|
|
// IE has a bug where it remove site absolute urls to relative ones if this is specified
|
|
if (s.document_base_url != tinymce.documentBaseURL)
|
|
t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
|
|
|
|
t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
|
|
|
|
if (tinymce.relaxedDomain)
|
|
t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
|
|
|
|
bi = s.body_id || 'tinymce';
|
|
if (bi.indexOf('=') != -1) {
|
|
bi = t.getParam('body_id', '', 'hash');
|
|
bi = bi[t.id] || bi;
|
|
}
|
|
|
|
bc = s.body_class || '';
|
|
if (bc.indexOf('=') != -1) {
|
|
bc = t.getParam('body_class', '', 'hash');
|
|
bc = bc[t.id] || '';
|
|
}
|
|
|
|
t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
|
|
|
|
// Domain relaxing enabled, then set document domain
|
|
if (tinymce.relaxedDomain) {
|
|
// We need to write the contents here in IE since multiple writes messes up refresh button and back button
|
|
if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
|
|
u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
|
|
else if (tinymce.isOpera)
|
|
u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
|
|
}
|
|
|
|
// Create iframe
|
|
n = DOM.add(o.iframeContainer, 'iframe', {
|
|
id : t.id + "_ifr",
|
|
src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
|
|
frameBorder : '0',
|
|
style : {
|
|
width : '100%',
|
|
height : h
|
|
}
|
|
});
|
|
|
|
t.contentAreaContainer = o.iframeContainer;
|
|
DOM.get(o.editorContainer).style.display = t.orgDisplay;
|
|
DOM.get(t.id).style.display = 'none';
|
|
|
|
if (!isIE || !tinymce.relaxedDomain)
|
|
t.setupIframe();
|
|
|
|
e = n = o = null; // Cleanup
|
|
},
|
|
|
|
setupIframe : function() {
|
|
var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
|
|
|
|
// Setup iframe body
|
|
if (!isIE || !tinymce.relaxedDomain) {
|
|
d.open();
|
|
d.write(t.iframeHTML);
|
|
d.close();
|
|
}
|
|
|
|
// Design mode needs to be added here Ctrl+A will fail otherwise
|
|
if (!isIE) {
|
|
try {
|
|
if (!s.readonly)
|
|
d.designMode = 'On';
|
|
} catch (ex) {
|
|
// Will fail on Gecko if the editor is placed in an hidden container element
|
|
// The design mode will be set ones the editor is focused
|
|
}
|
|
}
|
|
|
|
// IE needs to use contentEditable or it will display non secure items for HTTPS
|
|
if (isIE) {
|
|
// It will not steal focus if we hide it while setting contentEditable
|
|
b = t.getBody();
|
|
DOM.hide(b);
|
|
|
|
if (!s.readonly)
|
|
b.contentEditable = true;
|
|
|
|
DOM.show(b);
|
|
}
|
|
|
|
t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
|
|
keep_values : true,
|
|
url_converter : t.convertURL,
|
|
url_converter_scope : t,
|
|
hex_colors : s.force_hex_style_colors,
|
|
class_filter : s.class_filter,
|
|
update_styles : 1,
|
|
fix_ie_paragraphs : 1
|
|
});
|
|
|
|
t.serializer = new tinymce.dom.Serializer(extend(s, {
|
|
valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
|
|
dom : t.dom
|
|
}));
|
|
|
|
t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
|
|
|
|
t.forceBlocks = new tinymce.ForceBlocks(t, {
|
|
forced_root_block : s.forced_root_block
|
|
});
|
|
t.editorCommands = new tinymce.EditorCommands(t);
|
|
|
|
// Pass through
|
|
t.serializer.onPreProcess.add(function(se, o) {
|
|
return t.onPreProcess.dispatch(t, o, se);
|
|
});
|
|
|
|
t.serializer.onPostProcess.add(function(se, o) {
|
|
return t.onPostProcess.dispatch(t, o, se);
|
|
});
|
|
|
|
t.onPreInit.dispatch(t);
|
|
|
|
if (!s.gecko_spellcheck)
|
|
t.getBody().spellcheck = 0;
|
|
|
|
if (!s.readonly)
|
|
t._addEvents();
|
|
|
|
t.controlManager.onPostRender.dispatch(t, t.controlManager);
|
|
t.onPostRender.dispatch(t);
|
|
|
|
if (s.directionality)
|
|
t.getBody().dir = s.directionality;
|
|
|
|
if (s.nowrap)
|
|
t.getBody().style.whiteSpace = "nowrap";
|
|
|
|
if (s.custom_elements) {
|
|
function handleCustom(ed, o) {
|
|
each(explode(s.custom_elements), function(v) {
|
|
var n;
|
|
|
|
if (v.indexOf('~') === 0) {
|
|
v = v.substring(1);
|
|
n = 'span';
|
|
} else
|
|
n = 'div';
|
|
|
|
o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' mce_name="$1"$2>');
|
|
o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
|
|
});
|
|
};
|
|
|
|
t.onBeforeSetContent.add(handleCustom);
|
|
t.onPostProcess.add(function(ed, o) {
|
|
if (o.set)
|
|
handleCustom(ed, o);
|
|
});
|
|
}
|
|
|
|
if (s.handle_node_change_callback) {
|
|
t.onNodeChange.add(function(ed, cm, n) {
|
|
t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
|
|
});
|
|
}
|
|
|
|
if (s.save_callback) {
|
|
t.onSaveContent.add(function(ed, o) {
|
|
var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
|
|
|
|
if (h)
|
|
o.content = h;
|
|
});
|
|
}
|
|
|
|
if (s.onchange_callback) {
|
|
t.onChange.add(function(ed, l) {
|
|
t.execCallback('onchange_callback', t, l);
|
|
});
|
|
}
|
|
|
|
if (s.convert_newlines_to_brs) {
|
|
t.onBeforeSetContent.add(function(ed, o) {
|
|
if (o.initial)
|
|
o.content = o.content.replace(/\r?\n/g, '<br />');
|
|
});
|
|
}
|
|
|
|
if (s.fix_nesting && isIE) {
|
|
t.onBeforeSetContent.add(function(ed, o) {
|
|
o.content = t._fixNesting(o.content);
|
|
});
|
|
}
|
|
|
|
if (s.preformatted) {
|
|
t.onPostProcess.add(function(ed, o) {
|
|
o.content = o.content.replace(/^\s*<pre.*?>/, '');
|
|
o.content = o.content.replace(/<\/pre>\s*$/, '');
|
|
|
|
if (o.set)
|
|
o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
|
|
});
|
|
}
|
|
|
|
if (s.verify_css_classes) {
|
|
t.serializer.attribValueFilter = function(n, v) {
|
|
var s, cl;
|
|
|
|
if (n == 'class') {
|
|
// Build regexp for classes
|
|
if (!t.classesRE) {
|
|
cl = t.dom.getClasses();
|
|
|
|
if (cl.length > 0) {
|
|
s = '';
|
|
|
|
each (cl, function(o) {
|
|
s += (s ? '|' : '') + o['class'];
|
|
});
|
|
|
|
t.classesRE = new RegExp('(' + s + ')', 'gi');
|
|
}
|
|
}
|
|
|
|
return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
|
|
}
|
|
|
|
return v;
|
|
};
|
|
}
|
|
|
|
if (s.convert_fonts_to_spans)
|
|
t._convertFonts();
|
|
|
|
if (s.inline_styles)
|
|
t._convertInlineElements();
|
|
|
|
if (s.cleanup_callback) {
|
|
t.onBeforeSetContent.add(function(ed, o) {
|
|
o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
|
|
});
|
|
|
|
t.onPreProcess.add(function(ed, o) {
|
|
if (o.set)
|
|
t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
|
|
|
|
if (o.get)
|
|
t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
|
|
});
|
|
|
|
t.onPostProcess.add(function(ed, o) {
|
|
if (o.set)
|
|
o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
|
|
|
|
if (o.get)
|
|
o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
|
|
});
|
|
}
|
|
|
|
if (s.save_callback) {
|
|
t.onGetContent.add(function(ed, o) {
|
|
if (o.save)
|
|
o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
|
|
});
|
|
}
|
|
|
|
if (s.handle_event_callback) {
|
|
t.onEvent.add(function(ed, e, o) {
|
|
if (t.execCallback('handle_event_callback', e, ed, o) === false)
|
|
Event.cancel(e);
|
|
});
|
|
}
|
|
|
|
// Add visual aids when new contents is added
|
|
t.onSetContent.add(function() {
|
|
t.addVisual(t.getBody());
|
|
});
|
|
|
|
// Remove empty contents
|
|
if (s.padd_empty_editor) {
|
|
t.onPostProcess.add(function(ed, o) {
|
|
o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
|
|
});
|
|
}
|
|
|
|
if (isGecko) {
|
|
// Fix gecko link bug, when a link is placed at the end of block elements there is
|
|
// no way to move the caret behind the link. This fix adds a bogus br element after the link
|
|
function fixLinks(ed, o) {
|
|
each(ed.dom.select('a'), function(n) {
|
|
var pn = n.parentNode;
|
|
|
|
if (ed.dom.isBlock(pn) && pn.lastChild === n)
|
|
ed.dom.add(pn, 'br', {'mce_bogus' : 1});
|
|
});
|
|
};
|
|
|
|
t.onExecCommand.add(function(ed, cmd) {
|
|
if (cmd === 'CreateLink')
|
|
fixLinks(ed);
|
|
});
|
|
|
|
t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
|
|
|
|
if (!s.readonly) {
|
|
try {
|
|
// Design mode must be set here once again to fix a bug where
|
|
// Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
|
|
d.designMode = 'Off';
|
|
d.designMode = 'On';
|
|
} catch (ex) {
|
|
// Will fail on Gecko if the editor is placed in an hidden container element
|
|
// The design mode will be set ones the editor is focused
|
|
}
|
|
}
|
|
}
|
|
|
|
// A small timeout was needed since firefox will remove. Bug: #1838304
|
|
setTimeout(function () {
|
|
if (t.removed)
|
|
return;
|
|
|
|
t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
|
|
t.startContent = t.getContent({format : 'raw'});
|
|
t.undoManager.add({initial : true});
|
|
t.initialized = true;
|
|
|
|
t.onInit.dispatch(t);
|
|
t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
|
|
t.execCallback('init_instance_callback', t);
|
|
t.focus(true);
|
|
t.nodeChanged({initial : 1});
|
|
|
|
// Load specified content CSS last
|
|
if (s.content_css) {
|
|
tinymce.each(explode(s.content_css), function(u) {
|
|
t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
|
|
});
|
|
}
|
|
|
|
// Handle auto focus
|
|
if (s.auto_focus) {
|
|
setTimeout(function () {
|
|
var ed = EditorManager.get(s.auto_focus);
|
|
|
|
ed.selection.select(ed.getBody(), 1);
|
|
ed.selection.collapse(1);
|
|
ed.getWin().focus();
|
|
}, 100);
|
|
}
|
|
}, 1);
|
|
|
|
e = null;
|
|
},
|
|
|
|
|
|
focus : function(sf) {
|
|
var oed, t = this, ce = t.settings.content_editable;
|
|
|
|
if (!sf) {
|
|
// Is not content editable or the selection is outside the area in IE
|
|
// the IE statement is needed to avoid bluring if element selections inside layers since
|
|
// the layer is like it's own document in IE
|
|
if (!ce && (!isIE || t.selection.getNode().ownerDocument != t.getDoc()))
|
|
t.getWin().focus();
|
|
|
|
}
|
|
|
|
if (EditorManager.activeEditor != t) {
|
|
if ((oed = EditorManager.activeEditor) != null)
|
|
oed.onDeactivate.dispatch(oed, t);
|
|
|
|
t.onActivate.dispatch(t, oed);
|
|
}
|
|
|
|
EditorManager._setActive(t);
|
|
},
|
|
|
|
execCallback : function(n) {
|
|
var t = this, f = t.settings[n], s;
|
|
|
|
if (!f)
|
|
return;
|
|
|
|
// Look through lookup
|
|
if (t.callbackLookup && (s = t.callbackLookup[n])) {
|
|
f = s.func;
|
|
s = s.scope;
|
|
}
|
|
|
|
if (is(f, 'string')) {
|
|
s = f.replace(/\.\w+$/, '');
|
|
s = s ? tinymce.resolve(s) : 0;
|
|
f = tinymce.resolve(f);
|
|
t.callbackLookup = t.callbackLookup || {};
|
|
t.callbackLookup[n] = {func : f, scope : s};
|
|
}
|
|
|
|
return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
|
|
},
|
|
|
|
translate : function(s) {
|
|
var c = this.settings.language || 'en', i18n = EditorManager.i18n;
|
|
|
|
if (!s)
|
|
return '';
|
|
|
|
return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
|
|
return i18n[c + '.' + b] || '{#' + b + '}';
|
|
});
|
|
},
|
|
|
|
getLang : function(n, dv) {
|
|
return EditorManager.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
|
|
},
|
|
|
|
getParam : function(n, dv, ty) {
|
|
var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
|
|
|
|
if (ty === 'hash') {
|
|
o = {};
|
|
|
|
if (is(v, 'string')) {
|
|
each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
|
|
v = v.split('=');
|
|
|
|
if (v.length > 1)
|
|
o[tr(v[0])] = tr(v[1]);
|
|
else
|
|
o[tr(v[0])] = tr(v);
|
|
});
|
|
} else
|
|
o = v;
|
|
|
|
return o;
|
|
}
|
|
|
|
return v;
|
|
},
|
|
|
|
nodeChanged : function(o) {
|
|
var t = this, s = t.selection, n = s.getNode() || t.getBody();
|
|
|
|
// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
|
|
if (t.initialized) {
|
|
t.onNodeChange.dispatch(
|
|
t,
|
|
o ? o.controlManager || t.controlManager : t.controlManager,
|
|
isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n, // Fix for IE initial state
|
|
s.isCollapsed(),
|
|
o
|
|
);
|
|
}
|
|
},
|
|
|
|
addButton : function(n, s) {
|
|
var t = this;
|
|
|
|
t.buttons = t.buttons || {};
|
|
t.buttons[n] = s;
|
|
},
|
|
|
|
addCommand : function(n, f, s) {
|
|
this.execCommands[n] = {func : f, scope : s || this};
|
|
},
|
|
|
|
addQueryStateHandler : function(n, f, s) {
|
|
this.queryStateCommands[n] = {func : f, scope : s || this};
|
|
},
|
|
|
|
addQueryValueHandler : function(n, f, s) {
|
|
this.queryValueCommands[n] = {func : f, scope : s || this};
|
|
},
|
|
|
|
addShortcut : function(pa, desc, cmd_func, sc) {
|
|
var t = this, c;
|
|
|
|
if (!t.settings.custom_shortcuts)
|
|
return false;
|
|
|
|
t.shortcuts = t.shortcuts || {};
|
|
|
|
if (is(cmd_func, 'string')) {
|
|
c = cmd_func;
|
|
|
|
cmd_func = function() {
|
|
t.execCommand(c, false, null);
|
|
};
|
|
}
|
|
|
|
if (is(cmd_func, 'object')) {
|
|
c = cmd_func;
|
|
|
|
cmd_func = function() {
|
|
t.execCommand(c[0], c[1], c[2]);
|
|
};
|
|
}
|
|
|
|
each(explode(pa), function(pa) {
|
|
var o = {
|
|
func : cmd_func,
|
|
scope : sc || this,
|
|
desc : desc,
|
|
alt : false,
|
|
ctrl : false,
|
|
shift : false
|
|
};
|
|
|
|
each(explode(pa, '+'), function(v) {
|
|
switch (v) {
|
|
case 'alt':
|
|
case 'ctrl':
|
|
case 'shift':
|
|
o[v] = true;
|
|
break;
|
|
|
|
default:
|
|
o.charCode = v.charCodeAt(0);
|
|
o.keyCode = v.toUpperCase().charCodeAt(0);
|
|
}
|
|
});
|
|
|
|
t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
|
|
});
|
|
|
|
return true;
|
|
},
|
|
|
|
execCommand : function(cmd, ui, val, a) {
|
|
var t = this, s = 0, o, st;
|
|
|
|
if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
|
|
t.focus();
|
|
|
|
o = {};
|
|
t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
|
|
if (o.terminate)
|
|
return false;
|
|
|
|
// Command callback
|
|
if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
|
|
t.onExecCommand.dispatch(t, cmd, ui, val, a);
|
|
return true;
|
|
}
|
|
|
|
// Registred commands
|
|
if (o = t.execCommands[cmd]) {
|
|
st = o.func.call(o.scope, ui, val);
|
|
|
|
// Fall through on true
|
|
if (st !== true) {
|
|
t.onExecCommand.dispatch(t, cmd, ui, val, a);
|
|
return st;
|
|
}
|
|
}
|
|
|
|
// Plugin commands
|
|
each(t.plugins, function(p) {
|
|
if (p.execCommand && p.execCommand(cmd, ui, val)) {
|
|
t.onExecCommand.dispatch(t, cmd, ui, val, a);
|
|
s = 1;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if (s)
|
|
return true;
|
|
|
|
// Theme commands
|
|
if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
|
|
t.onExecCommand.dispatch(t, cmd, ui, val, a);
|
|
return true;
|
|
}
|
|
|
|
// Execute global commands
|
|
if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
|
|
t.onExecCommand.dispatch(t, cmd, ui, val, a);
|
|
return true;
|
|
}
|
|
|
|
// Editor commands
|
|
if (t.editorCommands.execCommand(cmd, ui, val)) {
|
|
t.onExecCommand.dispatch(t, cmd, ui, val, a);
|
|
return true;
|
|
}
|
|
|
|
// Browser commands
|
|
t.getDoc().execCommand(cmd, ui, val);
|
|
t.onExecCommand.dispatch(t, cmd, ui, val, a);
|
|
},
|
|
|
|
queryCommandState : function(c) {
|
|
var t = this, o, s;
|
|
|
|
// Is hidden then return undefined
|
|
if (t._isHidden())
|
|
return;
|
|
|
|
// Registred commands
|
|
if (o = t.queryStateCommands[c]) {
|
|
s = o.func.call(o.scope);
|
|
|
|
// Fall though on true
|
|
if (s !== true)
|
|
return s;
|
|
}
|
|
|
|
// Registred commands
|
|
o = t.editorCommands.queryCommandState(c);
|
|
if (o !== -1)
|
|
return o;
|
|
|
|
// Browser commands
|
|
try {
|
|
return this.getDoc().queryCommandState(c);
|
|
} catch (ex) {
|
|
// Fails sometimes see bug: 1896577
|
|
}
|
|
},
|
|
|
|
queryCommandValue : function(c) {
|
|
var t = this, o, s;
|
|
|
|
// Is hidden then return undefined
|
|
if (t._isHidden())
|
|
return;
|
|
|
|
// Registred commands
|
|
if (o = t.queryValueCommands[c]) {
|
|
s = o.func.call(o.scope);
|
|
|
|
// Fall though on true
|
|
if (s !== true)
|
|
return s;
|
|
}
|
|
|
|
// Registred commands
|
|
o = t.editorCommands.queryCommandValue(c);
|
|
if (is(o))
|
|
return o;
|
|
|
|
// Browser commands
|
|
try {
|
|
return this.getDoc().queryCommandValue(c);
|
|
} catch (ex) {
|
|
// Fails sometimes see bug: 1896577
|
|
}
|
|
},
|
|
|
|
show : function() {
|
|
var t = this;
|
|
|
|
DOM.show(t.getContainer());
|
|
DOM.hide(t.id);
|
|
t.load();
|
|
},
|
|
|
|
hide : function() {
|
|
var t = this, d = t.getDoc();
|
|
|
|
// Fixed bug where IE has a blinking cursor left from the editor
|
|
if (isIE && d)
|
|
d.execCommand('SelectAll');
|
|
|
|
// We must save before we hide so Safari doesn't crash
|
|
t.save();
|
|
DOM.hide(t.getContainer());
|
|
DOM.setStyle(t.id, 'display', t.orgDisplay);
|
|
},
|
|
|
|
isHidden : function() {
|
|
return !DOM.isHidden(this.id);
|
|
},
|
|
|
|
setProgressState : function(b, ti, o) {
|
|
this.onSetProgressState.dispatch(this, b, ti, o);
|
|
|
|
return b;
|
|
},
|
|
|
|
load : function(o) {
|
|
var t = this, e = t.getElement(), h;
|
|
|
|
if (e) {
|
|
o = o || {};
|
|
o.load = true;
|
|
|
|
// Double encode existing entities in the value
|
|
h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
|
|
o.element = e;
|
|
|
|
if (!o.no_events)
|
|
t.onLoadContent.dispatch(t, o);
|
|
|
|
o.element = e = null;
|
|
|
|
return h;
|
|
}
|
|
},
|
|
|
|
save : function(o) {
|
|
var t = this, e = t.getElement(), h, f;
|
|
|
|
if (!e || !t.initialized)
|
|
return;
|
|
|
|
o = o || {};
|
|
o.save = true;
|
|
|
|
// Add undo level will trigger onchange event
|
|
if (!o.no_events) {
|
|
t.undoManager.typing = 0;
|
|
t.undoManager.add();
|
|
}
|
|
|
|
o.element = e;
|
|
h = o.content = t.getContent(o);
|
|
|
|
if (!o.no_events)
|
|
t.onSaveContent.dispatch(t, o);
|
|
|
|
h = o.content;
|
|
|
|
if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
|
|
e.innerHTML = h;
|
|
|
|
// Update hidden form element
|
|
if (f = DOM.getParent(t.id, 'form')) {
|
|
each(f.elements, function(e) {
|
|
if (e.name == t.id) {
|
|
e.value = h;
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
} else
|
|
e.value = h;
|
|
|
|
o.element = e = null;
|
|
|
|
return h;
|
|
},
|
|
|
|
setContent : function(h, o) {
|
|
var t = this;
|
|
|
|
o = o || {};
|
|
o.format = o.format || 'html';
|
|
o.set = true;
|
|
o.content = h;
|
|
|
|
if (!o.no_events)
|
|
t.onBeforeSetContent.dispatch(t, o);
|
|
|
|
// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
|
|
// It will also be impossible to place the caret in the editor unless there is a BR element present
|
|
if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
|
|
o.content = t.dom.setHTML(t.getBody(), '<br mce_bogus="1" />');
|
|
o.format = 'raw';
|
|
}
|
|
|
|
o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
|
|
|
|
if (o.format != 'raw' && t.settings.cleanup) {
|
|
o.getInner = true;
|
|
o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
|
|
}
|
|
|
|
if (!o.no_events)
|
|
t.onSetContent.dispatch(t, o);
|
|
|
|
return o.content;
|
|
},
|
|
|
|
getContent : function(o) {
|
|
var t = this, h;
|
|
|
|
o = o || {};
|
|
o.format = o.format || 'html';
|
|
o.get = true;
|
|
|
|
if (!o.no_events)
|
|
t.onBeforeGetContent.dispatch(t, o);
|
|
|
|
if (o.format != 'raw' && t.settings.cleanup) {
|
|
o.getInner = true;
|
|
h = t.serializer.serialize(t.getBody(), o);
|
|
} else
|
|
h = t.getBody().innerHTML;
|
|
|
|
h = h.replace(/^\s*|\s*$/g, '');
|
|
o.content = h;
|
|
|
|
if (!o.no_events)
|
|
t.onGetContent.dispatch(t, o);
|
|
|
|
return o.content;
|
|
},
|
|
|
|
isDirty : function() {
|
|
var t = this;
|
|
|
|
return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
|
|
},
|
|
|
|
getContainer : function() {
|
|
var t = this;
|
|
|
|
if (!t.container)
|
|
t.container = DOM.get(t.editorContainer || t.id + '_parent');
|
|
|
|
return t.container;
|
|
},
|
|
|
|
getContentAreaContainer : function() {
|
|
return this.contentAreaContainer;
|
|
},
|
|
|
|
getElement : function() {
|
|
return DOM.get(this.settings.content_element || this.id);
|
|
},
|
|
|
|
getWin : function() {
|
|
var t = this, e;
|
|
|
|
if (!t.contentWindow) {
|
|
e = DOM.get(t.id + "_ifr");
|
|
|
|
if (e)
|
|
t.contentWindow = e.contentWindow;
|
|
}
|
|
|
|
return t.contentWindow;
|
|
},
|
|
|
|
getDoc : function() {
|
|
var t = this, w;
|
|
|
|
if (!t.contentDocument) {
|
|
w = t.getWin();
|
|
|
|
if (w)
|
|
t.contentDocument = w.document;
|
|
}
|
|
|
|
return t.contentDocument;
|
|
},
|
|
|
|
getBody : function() {
|
|
return this.bodyElement || this.getDoc().body;
|
|
},
|
|
|
|
convertURL : function(u, n, e) {
|
|
var t = this, s = t.settings;
|
|
|
|
// Use callback instead
|
|
if (s.urlconverter_callback)
|
|
return t.execCallback('urlconverter_callback', u, e, true, n);
|
|
|
|
// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
|
|
if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
|
|
return u;
|
|
|
|
// Convert to relative
|
|
if (s.relative_urls)
|
|
return t.documentBaseURI.toRelative(u);
|
|
|
|
// Convert to absolute
|
|
u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
|
|
|
|
return u;
|
|
},
|
|
|
|
addVisual : function(e) {
|
|
var t = this, s = t.settings;
|
|
|
|
e = e || t.getBody();
|
|
|
|
if (!is(t.hasVisual))
|
|
t.hasVisual = s.visual;
|
|
|
|
each(t.dom.select('table,a', e), function(e) {
|
|
var v;
|
|
|
|
switch (e.nodeName) {
|
|
case 'TABLE':
|
|
v = t.dom.getAttrib(e, 'border');
|
|
|
|
if (!v || v == '0') {
|
|
if (t.hasVisual)
|
|
t.dom.addClass(e, s.visual_table_class);
|
|
else
|
|
t.dom.removeClass(e, s.visual_table_class);
|
|
}
|
|
|
|
return;
|
|
|
|
case 'A':
|
|
v = t.dom.getAttrib(e, 'name');
|
|
|
|
if (v) {
|
|
if (t.hasVisual)
|
|
t.dom.addClass(e, 'mceItemAnchor');
|
|
else
|
|
t.dom.removeClass(e, 'mceItemAnchor');
|
|
}
|
|
|
|
return;
|
|
}
|
|
});
|
|
|
|
t.onVisualAid.dispatch(t, e, t.hasVisual);
|
|
},
|
|
|
|
remove : function() {
|
|
var t = this, e = t.getContainer();
|
|
|
|
t.removed = 1; // Cancels post remove event execution
|
|
t.hide();
|
|
|
|
t.execCallback('remove_instance_callback', t);
|
|
t.onRemove.dispatch(t);
|
|
|
|
// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
|
|
t.onExecCommand.listeners = [];
|
|
|
|
EditorManager.remove(t);
|
|
DOM.remove(e);
|
|
},
|
|
|
|
destroy : function(s) {
|
|
var t = this;
|
|
|
|
// One time is enough
|
|
if (t.destroyed)
|
|
return;
|
|
|
|
if (!s) {
|
|
tinymce.removeUnload(t.destroy);
|
|
tinyMCE.onBeforeUnload.remove(t._beforeUnload);
|
|
|
|
// Manual destroy
|
|
if (t.theme && t.theme.destroy)
|
|
t.theme.destroy();
|
|
|
|
// Destroy controls, selection and dom
|
|
t.controlManager.destroy();
|
|
t.selection.destroy();
|
|
t.dom.destroy();
|
|
|
|
// Remove all events
|
|
|
|
// Don't clear the window or document if content editable
|
|
// is enabled since other instances might still be present
|
|
if (!t.settings.content_editable) {
|
|
Event.clear(t.getWin());
|
|
Event.clear(t.getDoc());
|
|
}
|
|
|
|
Event.clear(t.getBody());
|
|
Event.clear(t.formElement);
|
|
}
|
|
|
|
if (t.formElement) {
|
|
t.formElement.submit = t.formElement._mceOldSubmit;
|
|
t.formElement._mceOldSubmit = null;
|
|
}
|
|
|
|
t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
|
|
|
|
if (t.selection)
|
|
t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
|
|
|
|
t.destroyed = 1;
|
|
},
|
|
|
|
// Internal functions
|
|
|
|
_addEvents : function() {
|
|
// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
|
|
var t = this, i, s = t.settings, lo = {
|
|
mouseup : 'onMouseUp',
|
|
mousedown : 'onMouseDown',
|
|
click : 'onClick',
|
|
keyup : 'onKeyUp',
|
|
keydown : 'onKeyDown',
|
|
keypress : 'onKeyPress',
|
|
submit : 'onSubmit',
|
|
reset : 'onReset',
|
|
contextmenu : 'onContextMenu',
|
|
dblclick : 'onDblClick',
|
|
paste : 'onPaste' // Doesn't work in all browsers yet
|
|
};
|
|
|
|
function eventHandler(e, o) {
|
|
var ty = e.type;
|
|
|
|
// Don't fire events when it's removed
|
|
if (t.removed)
|
|
return;
|
|
|
|
// Generic event handler
|
|
if (t.onEvent.dispatch(t, e, o) !== false) {
|
|
// Specific event handler
|
|
t[lo[e.fakeType || e.type]].dispatch(t, e, o);
|
|
}
|
|
};
|
|
|
|
// Add DOM events
|
|
each(lo, function(v, k) {
|
|
switch (k) {
|
|
case 'contextmenu':
|
|
if (tinymce.isOpera) {
|
|
// Fake contextmenu on Opera
|
|
t.dom.bind(t.getBody(), 'mousedown', function(e) {
|
|
if (e.ctrlKey) {
|
|
e.fakeType = 'contextmenu';
|
|
eventHandler(e);
|
|
}
|
|
});
|
|
} else
|
|
t.dom.bind(t.getBody(), k, eventHandler);
|
|
break;
|
|
|
|
case 'paste':
|
|
t.dom.bind(t.getBody(), k, function(e) {
|
|
eventHandler(e);
|
|
});
|
|
break;
|
|
|
|
case 'submit':
|
|
case 'reset':
|
|
t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
|
|
break;
|
|
|
|
default:
|
|
t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
|
|
}
|
|
});
|
|
|
|
t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
|
|
t.focus(true);
|
|
});
|
|
|
|
|
|
// Fixes bug where a specified document_base_uri could result in broken images
|
|
// This will also fix drag drop of images in Gecko
|
|
if (tinymce.isGecko) {
|
|
// Convert all images to absolute URLs
|
|
/* t.onSetContent.add(function(ed, o) {
|
|
each(ed.dom.select('img'), function(e) {
|
|
var v;
|
|
|
|
if (v = e.getAttribute('mce_src'))
|
|
e.src = t.documentBaseURI.toAbsolute(v);
|
|
})
|
|
});*/
|
|
|
|
t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
|
|
var v;
|
|
|
|
e = e.target;
|
|
|
|
if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('mce_src')))
|
|
e.src = t.documentBaseURI.toAbsolute(v);
|
|
});
|
|
}
|
|
|
|
// Set various midas options in Gecko
|
|
if (isGecko) {
|
|
function setOpts() {
|
|
var t = this, d = t.getDoc(), s = t.settings;
|
|
|
|
if (isGecko && !s.readonly) {
|
|
if (t._isHidden()) {
|
|
try {
|
|
if (!s.content_editable)
|
|
d.designMode = 'On';
|
|
} catch (ex) {
|
|
// Fails if it's hidden
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Try new Gecko method
|
|
d.execCommand("styleWithCSS", 0, false);
|
|
} catch (ex) {
|
|
// Use old method
|
|
if (!t._isHidden())
|
|
try {d.execCommand("useCSS", 0, true);} catch (ex) {}
|
|
}
|
|
|
|
if (!s.table_inline_editing)
|
|
try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
|
|
|
|
if (!s.object_resizing)
|
|
try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
|
|
}
|
|
};
|
|
|
|
t.onBeforeExecCommand.add(setOpts);
|
|
t.onMouseDown.add(setOpts);
|
|
}
|
|
|
|
// Add node change handlers
|
|
t.onMouseUp.add(t.nodeChanged);
|
|
t.onClick.add(t.nodeChanged);
|
|
t.onKeyUp.add(function(ed, e) {
|
|
var c = e.keyCode;
|
|
|
|
if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
|
|
t.nodeChanged();
|
|
});
|
|
|
|
// Add reset handler
|
|
t.onReset.add(function() {
|
|
t.setContent(t.startContent, {format : 'raw'});
|
|
});
|
|
|
|
// Add shortcuts
|
|
if (s.custom_shortcuts) {
|
|
if (s.custom_undo_redo_keyboard_shortcuts) {
|
|
t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
|
|
t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
|
|
}
|
|
|
|
// Add default shortcuts for gecko
|
|
if (isGecko) {
|
|
t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
|
|
t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
|
|
t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
|
|
}
|
|
|
|
// BlockFormat shortcuts keys
|
|
for (i=1; i<=6; i++)
|
|
t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, '<h' + i + '>']);
|
|
|
|
t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
|
|
t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
|
|
t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
|
|
|
|
function find(e) {
|
|
var v = null;
|
|
|
|
if (!e.altKey && !e.ctrlKey && !e.metaKey)
|
|
return v;
|
|
|
|
each(t.shortcuts, function(o) {
|
|
if (tinymce.isMac && o.ctrl != e.metaKey)
|
|
return;
|
|
else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
|
|
return;
|
|
|
|
if (o.alt != e.altKey)
|
|
return;
|
|
|
|
if (o.shift != e.shiftKey)
|
|
return;
|
|
|
|
if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
|
|
v = o;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return v;
|
|
};
|
|
|
|
t.onKeyUp.add(function(ed, e) {
|
|
var o = find(e);
|
|
|
|
if (o)
|
|
return Event.cancel(e);
|
|
});
|
|
|
|
t.onKeyPress.add(function(ed, e) {
|
|
var o = find(e);
|
|
|
|
if (o)
|
|
return Event.cancel(e);
|
|
});
|
|
|
|
t.onKeyDown.add(function(ed, e) {
|
|
var o = find(e);
|
|
|
|
if (o) {
|
|
o.func.call(o.scope);
|
|
return Event.cancel(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (tinymce.isIE) {
|
|
// Fix so resize will only update the width and height attributes not the styles of an image
|
|
// It will also block mceItemNoResize items
|
|
t.dom.bind(t.getDoc(), 'controlselect', function(e) {
|
|
var re = t.resizeInfo, cb;
|
|
|
|
e = e.target;
|
|
|
|
// Don't do this action for non image elements
|
|
if (e.nodeName !== 'IMG')
|
|
return;
|
|
|
|
if (re)
|
|
t.dom.unbind(re.node, re.ev, re.cb);
|
|
|
|
if (!t.dom.hasClass(e, 'mceItemNoResize')) {
|
|
ev = 'resizeend';
|
|
cb = t.dom.bind(e, ev, function(e) {
|
|
var v;
|
|
|
|
e = e.target;
|
|
|
|
if (v = t.dom.getStyle(e, 'width')) {
|
|
t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
|
|
t.dom.setStyle(e, 'width', '');
|
|
}
|
|
|
|
if (v = t.dom.getStyle(e, 'height')) {
|
|
t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
|
|
t.dom.setStyle(e, 'height', '');
|
|
}
|
|
});
|
|
} else {
|
|
ev = 'resizestart';
|
|
cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
|
|
}
|
|
|
|
re = t.resizeInfo = {
|
|
node : e,
|
|
ev : ev,
|
|
cb : cb
|
|
};
|
|
});
|
|
|
|
t.onKeyDown.add(function(ed, e) {
|
|
switch (e.keyCode) {
|
|
case 8:
|
|
// Fix IE control + backspace browser bug
|
|
if (t.selection.getRng().item) {
|
|
t.selection.getRng().item(0).removeNode();
|
|
return Event.cancel(e);
|
|
}
|
|
}
|
|
});
|
|
|
|
/*if (t.dom.boxModel) {
|
|
t.getBody().style.height = '100%';
|
|
|
|
Event.add(t.getWin(), 'resize', function(e) {
|
|
var docElm = t.getDoc().documentElement;
|
|
|
|
docElm.style.height = (docElm.offsetHeight - 10) + 'px';
|
|
});
|
|
}*/
|
|
}
|
|
|
|
if (tinymce.isOpera) {
|
|
t.onClick.add(function(ed, e) {
|
|
Event.prevent(e);
|
|
});
|
|
}
|
|
|
|
// Add custom undo/redo handlers
|
|
if (s.custom_undo_redo) {
|
|
function addUndo() {
|
|
t.undoManager.typing = 0;
|
|
t.undoManager.add();
|
|
};
|
|
|
|
// Add undo level on editor blur
|
|
if (tinymce.isIE) {
|
|
t.dom.bind(t.getWin(), 'blur', function(e) {
|
|
var n;
|
|
|
|
// Check added for fullscreen bug
|
|
if (t.selection) {
|
|
n = t.selection.getNode();
|
|
|
|
// Add undo level is selection was lost to another document
|
|
if (!t.removed && n.ownerDocument && n.ownerDocument != t.getDoc())
|
|
addUndo();
|
|
}
|
|
});
|
|
} else {
|
|
t.dom.bind(t.getDoc(), 'blur', function() {
|
|
if (t.selection && !t.removed)
|
|
addUndo();
|
|
});
|
|
}
|
|
|
|
t.onMouseDown.add(addUndo);
|
|
|
|
t.onKeyUp.add(function(ed, e) {
|
|
if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) {
|
|
t.undoManager.typing = 0;
|
|
t.undoManager.add();
|
|
}
|
|
});
|
|
|
|
t.onKeyDown.add(function(ed, e) {
|
|
// Is caracter positon keys
|
|
if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
|
|
if (t.undoManager.typing) {
|
|
t.undoManager.add();
|
|
t.undoManager.typing = 0;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!t.undoManager.typing) {
|
|
t.undoManager.add();
|
|
t.undoManager.typing = 1;
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
_convertInlineElements : function() {
|
|
var t = this, s = t.settings, dom = t.dom, v, e, na, st, sp;
|
|
|
|
function convert(ed, o) {
|
|
if (!s.inline_styles)
|
|
return;
|
|
|
|
if (o.get) {
|
|
each(t.dom.select('table,u,strike', o.node), function(n) {
|
|
switch (n.nodeName) {
|
|
case 'TABLE':
|
|
if (v = dom.getAttrib(n, 'height')) {
|
|
dom.setStyle(n, 'height', v);
|
|
dom.setAttrib(n, 'height', '');
|
|
}
|
|
break;
|
|
|
|
case 'U':
|
|
case 'STRIKE':
|
|
//sp = dom.create('span', {style : dom.getAttrib(n, 'style')});
|
|
n.style.textDecoration = n.nodeName == 'U' ? 'underline' : 'line-through';
|
|
dom.setAttrib(n, 'mce_style', '');
|
|
dom.setAttrib(n, 'mce_name', 'span');
|
|
break;
|
|
}
|
|
});
|
|
} else if (o.set) {
|
|
each(t.dom.select('table,span', o.node).reverse(), function(n) {
|
|
if (n.nodeName == 'TABLE') {
|
|
if (v = dom.getStyle(n, 'height'))
|
|
dom.setAttrib(n, 'height', v.replace(/[^0-9%]+/g, ''));
|
|
} else {
|
|
// Convert spans to elements
|
|
if (n.style.textDecoration == 'underline')
|
|
na = 'u';
|
|
else if (n.style.textDecoration == 'line-through')
|
|
na = 'strike';
|
|
else
|
|
na = '';
|
|
|
|
if (na) {
|
|
n.style.textDecoration = '';
|
|
dom.setAttrib(n, 'mce_style', '');
|
|
|
|
e = dom.create(na, {
|
|
style : dom.getAttrib(n, 'style')
|
|
});
|
|
|
|
dom.replace(e, n, 1);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
t.onPreProcess.add(convert);
|
|
|
|
if (!s.cleanup_on_startup) {
|
|
t.onSetContent.add(function(ed, o) {
|
|
if (o.initial)
|
|
convert(t, {node : t.getBody(), set : 1});
|
|
});
|
|
}
|
|
},
|
|
|
|
_convertFonts : function() {
|
|
var t = this, s = t.settings, dom = t.dom, fz, fzn, sl, cl;
|
|
|
|
// No need
|
|
if (!s.inline_styles)
|
|
return;
|
|
|
|
// Font pt values and font size names
|
|
fz = [8, 10, 12, 14, 18, 24, 36];
|
|
fzn = ['xx-small', 'x-small','small','medium','large','x-large', 'xx-large'];
|
|
|
|
if (sl = s.font_size_style_values)
|
|
sl = explode(sl);
|
|
|
|
if (cl = s.font_size_classes)
|
|
cl = explode(cl);
|
|
|
|
function process(no) {
|
|
var n, sp, nl, x;
|
|
|
|
// Keep unit tests happy
|
|
if (!s.inline_styles)
|
|
return;
|
|
|
|
nl = t.dom.select('font', no);
|
|
for (x = nl.length - 1; x >= 0; x--) {
|
|
n = nl[x];
|
|
|
|
sp = dom.create('span', {
|
|
style : dom.getAttrib(n, 'style'),
|
|
'class' : dom.getAttrib(n, 'class')
|
|
});
|
|
|
|
dom.setStyles(sp, {
|
|
fontFamily : dom.getAttrib(n, 'face'),
|
|
color : dom.getAttrib(n, 'color'),
|
|
backgroundColor : n.style.backgroundColor
|
|
});
|
|
|
|
if (n.size) {
|
|
if (sl)
|
|
dom.setStyle(sp, 'fontSize', sl[parseInt(n.size) - 1]);
|
|
else
|
|
dom.setAttrib(sp, 'class', cl[parseInt(n.size) - 1]);
|
|
}
|
|
|
|
dom.setAttrib(sp, 'mce_style', '');
|
|
dom.replace(sp, n, 1);
|
|
}
|
|
};
|
|
|
|
// Run on cleanup
|
|
t.onPreProcess.add(function(ed, o) {
|
|
if (o.get)
|
|
process(o.node);
|
|
});
|
|
|
|
t.onSetContent.add(function(ed, o) {
|
|
if (o.initial)
|
|
process(o.node);
|
|
});
|
|
},
|
|
|
|
_isHidden : function() {
|
|
var s;
|
|
|
|
if (!isGecko)
|
|
return 0;
|
|
|
|
// Weird, wheres that cursor selection?
|
|
s = this.selection.getSel();
|
|
return (!s || !s.rangeCount || s.rangeCount == 0);
|
|
},
|
|
|
|
// Fix for bug #1867292
|
|
_fixNesting : function(s) {
|
|
var d = [], i;
|
|
|
|
s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
|
|
var e;
|
|
|
|
// Handle end element
|
|
if (b === '/') {
|
|
if (!d.length)
|
|
return '';
|
|
|
|
if (c !== d[d.length - 1].tag) {
|
|
for (i=d.length - 1; i>=0; i--) {
|
|
if (d[i].tag === c) {
|
|
d[i].close = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return '';
|
|
} else {
|
|
d.pop();
|
|
|
|
if (d.length && d[d.length - 1].close) {
|
|
a = a + '</' + d[d.length - 1].tag + '>';
|
|
d.pop();
|
|
}
|
|
}
|
|
} else {
|
|
// Ignore these
|
|
if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
|
|
return a;
|
|
|
|
// Ignore closed ones
|
|
if (/\/>$/.test(a))
|
|
return a;
|
|
|
|
d.push({tag : c}); // Push start element
|
|
}
|
|
|
|
return a;
|
|
});
|
|
|
|
// End all open tags
|
|
for (i=d.length - 1; i>=0; i--)
|
|
s += '</' + d[i].tag + '>';
|
|
|
|
return s;
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
var each = tinymce.each, isIE = tinymce.isIE, isGecko = tinymce.isGecko, isOpera = tinymce.isOpera, isWebKit = tinymce.isWebKit;
|
|
|
|
tinymce.create('tinymce.EditorCommands', {
|
|
EditorCommands : function(ed) {
|
|
this.editor = ed;
|
|
},
|
|
|
|
execCommand : function(cmd, ui, val) {
|
|
var t = this, ed = t.editor, f;
|
|
|
|
switch (cmd) {
|
|
// Ignore these
|
|
case 'mceResetDesignMode':
|
|
case 'mceBeginUndoLevel':
|
|
return true;
|
|
|
|
// Ignore these
|
|
case 'unlink':
|
|
t.UnLink();
|
|
return true;
|
|
|
|
// Bundle these together
|
|
case 'JustifyLeft':
|
|
case 'JustifyCenter':
|
|
case 'JustifyRight':
|
|
case 'JustifyFull':
|
|
t.mceJustify(cmd, cmd.substring(7).toLowerCase());
|
|
return true;
|
|
|
|
default:
|
|
f = this[cmd];
|
|
|
|
if (f) {
|
|
f.call(this, ui, val);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
Indent : function() {
|
|
var ed = this.editor, d = ed.dom, s = ed.selection, e, iv, iu;
|
|
|
|
// Setup indent level
|
|
iv = ed.settings.indentation;
|
|
iu = /[a-z%]+$/i.exec(iv);
|
|
iv = parseInt(iv);
|
|
|
|
if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) {
|
|
each(s.getSelectedBlocks(), function(e) {
|
|
d.setStyle(e, 'paddingLeft', (parseInt(e.style.paddingLeft || 0) + iv) + iu);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
ed.getDoc().execCommand('Indent', false, null);
|
|
|
|
if (isIE) {
|
|
d.getParent(s.getNode(), function(n) {
|
|
if (n.nodeName == 'BLOCKQUOTE') {
|
|
n.dir = n.style.cssText = '';
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
Outdent : function() {
|
|
var ed = this.editor, d = ed.dom, s = ed.selection, e, v, iv, iu;
|
|
|
|
// Setup indent level
|
|
iv = ed.settings.indentation;
|
|
iu = /[a-z%]+$/i.exec(iv);
|
|
iv = parseInt(iv);
|
|
|
|
if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) {
|
|
each(s.getSelectedBlocks(), function(e) {
|
|
v = Math.max(0, parseInt(e.style.paddingLeft || 0) - iv);
|
|
d.setStyle(e, 'paddingLeft', v ? v + iu : '');
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
ed.getDoc().execCommand('Outdent', false, null);
|
|
},
|
|
|
|
/*
|
|
mceSetAttribute : function(u, v) {
|
|
var ed = this.editor, d = ed.dom, e;
|
|
|
|
if (e = d.getParent(ed.selection.getNode(), d.isBlock))
|
|
d.setAttrib(e, v.name, v.value);
|
|
},
|
|
*/
|
|
mceSetContent : function(u, v) {
|
|
this.editor.setContent(v);
|
|
},
|
|
|
|
mceToggleVisualAid : function() {
|
|
var ed = this.editor;
|
|
|
|
ed.hasVisual = !ed.hasVisual;
|
|
ed.addVisual();
|
|
},
|
|
|
|
mceReplaceContent : function(u, v) {
|
|
var s = this.editor.selection;
|
|
|
|
s.setContent(v.replace(/\{\$selection\}/g, s.getContent({format : 'text'})));
|
|
},
|
|
|
|
mceInsertLink : function(u, v) {
|
|
var ed = this.editor, s = ed.selection, e = ed.dom.getParent(s.getNode(), 'a');
|
|
|
|
if (tinymce.is(v, 'string'))
|
|
v = {href : v};
|
|
|
|
function set(e) {
|
|
each(v, function(v, k) {
|
|
ed.dom.setAttrib(e, k, v);
|
|
});
|
|
};
|
|
|
|
if (!e) {
|
|
ed.execCommand('CreateLink', false, 'javascript:mctmp(0);');
|
|
each(ed.dom.select('a[href=javascript:mctmp(0);]'), function(e) {
|
|
set(e);
|
|
});
|
|
} else {
|
|
if (v.href)
|
|
set(e);
|
|
else
|
|
ed.dom.remove(e, 1);
|
|
}
|
|
},
|
|
|
|
UnLink : function() {
|
|
var ed = this.editor, s = ed.selection;
|
|
|
|
if (s.isCollapsed())
|
|
s.select(s.getNode());
|
|
|
|
ed.getDoc().execCommand('unlink', false, null);
|
|
s.collapse(0);
|
|
},
|
|
|
|
FontName : function(u, v) {
|
|
var t = this, ed = t.editor, s = ed.selection, e;
|
|
|
|
if (!v) {
|
|
if (s.isCollapsed())
|
|
s.select(s.getNode());
|
|
} else {
|
|
if (ed.settings.convert_fonts_to_spans)
|
|
t._applyInlineStyle('span', {style : {fontFamily : v}});
|
|
else
|
|
ed.getDoc().execCommand('FontName', false, v);
|
|
}
|
|
},
|
|
|
|
FontSize : function(u, v) {
|
|
var ed = this.editor, s = ed.settings, fc, fs;
|
|
|
|
// Use style options instead
|
|
if (s.convert_fonts_to_spans && v >= 1 && v <= 7) {
|
|
fs = tinymce.explode(s.font_size_style_values);
|
|
fc = tinymce.explode(s.font_size_classes);
|
|
|
|
if (fc)
|
|
v = fc[v - 1] || v;
|
|
else
|
|
v = fs[v - 1] || v;
|
|
}
|
|
|
|
if (v >= 1 && v <= 7)
|
|
ed.getDoc().execCommand('FontSize', false, v);
|
|
else
|
|
this._applyInlineStyle('span', {style : {fontSize : v}});
|
|
},
|
|
|
|
queryCommandValue : function(c) {
|
|
var f = this['queryValue' + c];
|
|
|
|
if (f)
|
|
return f.call(this, c);
|
|
|
|
return false;
|
|
},
|
|
|
|
queryCommandState : function(cmd) {
|
|
var f;
|
|
|
|
switch (cmd) {
|
|
// Bundle these together
|
|
case 'JustifyLeft':
|
|
case 'JustifyCenter':
|
|
case 'JustifyRight':
|
|
case 'JustifyFull':
|
|
return this.queryStateJustify(cmd, cmd.substring(7).toLowerCase());
|
|
|
|
default:
|
|
if (f = this['queryState' + cmd])
|
|
return f.call(this, cmd);
|
|
}
|
|
|
|
return -1;
|
|
},
|
|
|
|
_queryState : function(c) {
|
|
try {
|
|
return this.editor.getDoc().queryCommandState(c);
|
|
} catch (ex) {
|
|
// Ignore exception
|
|
}
|
|
},
|
|
|
|
_queryVal : function(c) {
|
|
try {
|
|
return this.editor.getDoc().queryCommandValue(c);
|
|
} catch (ex) {
|
|
// Ignore exception
|
|
}
|
|
},
|
|
|
|
queryValueFontSize : function() {
|
|
var ed = this.editor, v = 0, p;
|
|
|
|
if (p = ed.dom.getParent(ed.selection.getNode(), 'span'))
|
|
v = p.style.fontSize;
|
|
|
|
if (!v && (isOpera || isWebKit)) {
|
|
if (p = ed.dom.getParent(ed.selection.getNode(), 'font'))
|
|
v = p.size;
|
|
|
|
return v;
|
|
}
|
|
|
|
return v || this._queryVal('FontSize');
|
|
},
|
|
|
|
queryValueFontName : function() {
|
|
var ed = this.editor, v = 0, p;
|
|
|
|
if (p = ed.dom.getParent(ed.selection.getNode(), 'font'))
|
|
v = p.face;
|
|
|
|
if (p = ed.dom.getParent(ed.selection.getNode(), 'span'))
|
|
v = p.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
|
|
|
|
if (!v)
|
|
v = this._queryVal('FontName');
|
|
|
|
return v;
|
|
},
|
|
|
|
mceJustify : function(c, v) {
|
|
var ed = this.editor, se = ed.selection, n = se.getNode(), nn = n.nodeName, bl, nb, dom = ed.dom, rm;
|
|
|
|
if (ed.settings.inline_styles && this.queryStateJustify(c, v))
|
|
rm = 1;
|
|
|
|
bl = dom.getParent(n, ed.dom.isBlock);
|
|
|
|
if (nn == 'IMG') {
|
|
if (v == 'full')
|
|
return;
|
|
|
|
if (rm) {
|
|
if (v == 'center')
|
|
dom.setStyle(bl || n.parentNode, 'textAlign', '');
|
|
|
|
dom.setStyle(n, 'float', '');
|
|
this.mceRepaint();
|
|
return;
|
|
}
|
|
|
|
if (v == 'center') {
|
|
// Do not change table elements
|
|
if (bl && /^(TD|TH)$/.test(bl.nodeName))
|
|
bl = 0;
|
|
|
|
if (!bl || bl.childNodes.length > 1) {
|
|
nb = dom.create('p');
|
|
nb.appendChild(n.cloneNode(false));
|
|
|
|
if (bl)
|
|
dom.insertAfter(nb, bl);
|
|
else
|
|
dom.insertAfter(nb, n);
|
|
|
|
dom.remove(n);
|
|
n = nb.firstChild;
|
|
bl = nb;
|
|
}
|
|
|
|
dom.setStyle(bl, 'textAlign', v);
|
|
dom.setStyle(n, 'float', '');
|
|
} else {
|
|
dom.setStyle(n, 'float', v);
|
|
dom.setStyle(bl || n.parentNode, 'textAlign', '');
|
|
}
|
|
|
|
this.mceRepaint();
|
|
return;
|
|
}
|
|
|
|
// Handle the alignment outselfs, less quirks in all browsers
|
|
if (ed.settings.inline_styles && ed.settings.forced_root_block) {
|
|
if (rm)
|
|
v = '';
|
|
|
|
each(se.getSelectedBlocks(dom.getParent(se.getStart(), dom.isBlock), dom.getParent(se.getEnd(), dom.isBlock)), function(e) {
|
|
dom.setAttrib(e, 'align', '');
|
|
dom.setStyle(e, 'textAlign', v == 'full' ? 'justify' : v);
|
|
});
|
|
|
|
return;
|
|
} else if (!rm)
|
|
ed.getDoc().execCommand(c, false, null);
|
|
|
|
if (ed.settings.inline_styles) {
|
|
if (rm) {
|
|
dom.getParent(ed.selection.getNode(), function(n) {
|
|
if (n.style && n.style.textAlign)
|
|
dom.setStyle(n, 'textAlign', '');
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
each(dom.select('*'), function(n) {
|
|
var v = n.align;
|
|
|
|
if (v) {
|
|
if (v == 'full')
|
|
v = 'justify';
|
|
|
|
dom.setStyle(n, 'textAlign', v);
|
|
dom.setAttrib(n, 'align', '');
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
mceSetCSSClass : function(u, v) {
|
|
this.mceSetStyleInfo(0, {command : 'setattrib', name : 'class', value : v});
|
|
},
|
|
|
|
getSelectedElement : function() {
|
|
var t = this, ed = t.editor, dom = ed.dom, se = ed.selection, r = se.getRng(), r1, r2, sc, ec, so, eo, e, sp, ep, re;
|
|
|
|
if (se.isCollapsed() || r.item)
|
|
return se.getNode();
|
|
|
|
// Setup regexp
|
|
re = ed.settings.merge_styles_invalid_parents;
|
|
if (tinymce.is(re, 'string'))
|
|
re = new RegExp(re, 'i');
|
|
|
|
if (isIE) {
|
|
r1 = r.duplicate();
|
|
r1.collapse(true);
|
|
sc = r1.parentElement();
|
|
|
|
r2 = r.duplicate();
|
|
r2.collapse(false);
|
|
ec = r2.parentElement();
|
|
|
|
if (sc != ec) {
|
|
r1.move('character', 1);
|
|
sc = r1.parentElement();
|
|
}
|
|
|
|
if (sc == ec) {
|
|
r1 = r.duplicate();
|
|
r1.moveToElementText(sc);
|
|
|
|
if (r1.compareEndPoints('StartToStart', r) == 0 && r1.compareEndPoints('EndToEnd', r) == 0)
|
|
return re && re.test(sc.nodeName) ? null : sc;
|
|
}
|
|
} else {
|
|
function getParent(n) {
|
|
return dom.getParent(n, '*');
|
|
};
|
|
|
|
sc = r.startContainer;
|
|
ec = r.endContainer;
|
|
so = r.startOffset;
|
|
eo = r.endOffset;
|
|
|
|
if (!r.collapsed) {
|
|
if (sc == ec) {
|
|
if (so - eo < 2) {
|
|
if (sc.hasChildNodes()) {
|
|
sp = sc.childNodes[so];
|
|
return re && re.test(sp.nodeName) ? null : sp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sc.nodeType != 3 || ec.nodeType != 3)
|
|
return null;
|
|
|
|
if (so == 0) {
|
|
sp = getParent(sc);
|
|
|
|
if (sp && sp.firstChild != sc)
|
|
sp = null;
|
|
}
|
|
|
|
if (so == sc.nodeValue.length) {
|
|
e = sc.nextSibling;
|
|
|
|
if (e && e.nodeType == 1)
|
|
sp = sc.nextSibling;
|
|
}
|
|
|
|
if (eo == 0) {
|
|
e = ec.previousSibling;
|
|
|
|
if (e && e.nodeType == 1)
|
|
ep = e;
|
|
}
|
|
|
|
if (eo == ec.nodeValue.length) {
|
|
ep = getParent(ec);
|
|
|
|
if (ep && ep.lastChild != ec)
|
|
ep = null;
|
|
}
|
|
|
|
// Same element
|
|
if (sp == ep)
|
|
return re && sp && re.test(sp.nodeName) ? null : sp;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
mceSetStyleInfo : function(u, v) {
|
|
var t = this, ed = t.editor, d = ed.getDoc(), dom = ed.dom, e, b, s = ed.selection, nn = v.wrapper || 'span', b = s.getBookmark(), re;
|
|
|
|
function set(n, e) {
|
|
if (n.nodeType == 1) {
|
|
switch (v.command) {
|
|
case 'setattrib':
|
|
return dom.setAttrib(n, v.name, v.value);
|
|
|
|
case 'setstyle':
|
|
return dom.setStyle(n, v.name, v.value);
|
|
|
|
case 'removeformat':
|
|
return dom.setAttrib(n, 'class', '');
|
|
}
|
|
}
|
|
};
|
|
|
|
// Setup regexp
|
|
re = ed.settings.merge_styles_invalid_parents;
|
|
if (tinymce.is(re, 'string'))
|
|
re = new RegExp(re, 'i');
|
|
|
|
// Set style info on selected element
|
|
if ((e = t.getSelectedElement()) && !ed.settings.force_span_wrappers)
|
|
set(e, 1);
|
|
else {
|
|
// Generate wrappers and set styles on them
|
|
d.execCommand('FontName', false, '__');
|
|
each(dom.select('span,font'), function(n) {
|
|
var sp, e;
|
|
|
|
if (dom.getAttrib(n, 'face') == '__' || n.style.fontFamily === '__') {
|
|
sp = dom.create(nn, {mce_new : '1'});
|
|
|
|
set(sp);
|
|
|
|
each (n.childNodes, function(n) {
|
|
sp.appendChild(n.cloneNode(true));
|
|
});
|
|
|
|
dom.replace(sp, n);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Remove wrappers inside new ones
|
|
each(dom.select(nn).reverse(), function(n) {
|
|
var p = n.parentNode;
|
|
|
|
// Check if it's an old span in a new wrapper
|
|
if (!dom.getAttrib(n, 'mce_new')) {
|
|
// Find new wrapper
|
|
p = dom.getParent(n, '*[mce_new]');
|
|
|
|
if (p)
|
|
dom.remove(n, 1);
|
|
}
|
|
});
|
|
|
|
// Merge wrappers with parent wrappers
|
|
each(dom.select(nn).reverse(), function(n) {
|
|
var p = n.parentNode;
|
|
|
|
if (!p || !dom.getAttrib(n, 'mce_new'))
|
|
return;
|
|
|
|
if (ed.settings.force_span_wrappers && p.nodeName != 'SPAN')
|
|
return;
|
|
|
|
// Has parent of the same type and only child
|
|
if (p.nodeName == nn.toUpperCase() && p.childNodes.length == 1)
|
|
return dom.remove(p, 1);
|
|
|
|
// Has parent that is more suitable to have the class and only child
|
|
if (n.nodeType == 1 && (!re || !re.test(p.nodeName)) && p.childNodes.length == 1) {
|
|
set(p); // Set style info on parent instead
|
|
dom.setAttrib(n, 'class', '');
|
|
}
|
|
});
|
|
|
|
// Remove empty wrappers
|
|
each(dom.select(nn).reverse(), function(n) {
|
|
if (dom.getAttrib(n, 'mce_new') || (dom.getAttribs(n).length <= 1 && n.className === '')) {
|
|
if (!dom.getAttrib(n, 'class') && !dom.getAttrib(n, 'style'))
|
|
return dom.remove(n, 1);
|
|
|
|
dom.setAttrib(n, 'mce_new', ''); // Remove mce_new marker
|
|
}
|
|
});
|
|
|
|
s.moveToBookmark(b);
|
|
},
|
|
|
|
queryStateJustify : function(c, v) {
|
|
var ed = this.editor, n = ed.selection.getNode(), dom = ed.dom;
|
|
|
|
if (n && n.nodeName == 'IMG') {
|
|
if (dom.getStyle(n, 'float') == v)
|
|
return 1;
|
|
|
|
return n.parentNode.style.textAlign == v;
|
|
}
|
|
|
|
n = dom.getParent(ed.selection.getStart(), function(n) {
|
|
return n.nodeType == 1 && n.style.textAlign;
|
|
});
|
|
|
|
if (v == 'full')
|
|
v = 'justify';
|
|
|
|
if (ed.settings.inline_styles)
|
|
return (n && n.style.textAlign == v);
|
|
|
|
return this._queryState(c);
|
|
},
|
|
|
|
ForeColor : function(ui, v) {
|
|
var ed = this.editor;
|
|
|
|
if (ed.settings.convert_fonts_to_spans) {
|
|
this._applyInlineStyle('span', {style : {color : v}});
|
|
return;
|
|
} else
|
|
ed.getDoc().execCommand('ForeColor', false, v);
|
|
},
|
|
|
|
HiliteColor : function(ui, val) {
|
|
var t = this, ed = t.editor, d = ed.getDoc();
|
|
|
|
if (ed.settings.convert_fonts_to_spans) {
|
|
this._applyInlineStyle('span', {style : {backgroundColor : val}});
|
|
return;
|
|
}
|
|
|
|
function set(s) {
|
|
if (!isGecko)
|
|
return;
|
|
|
|
try {
|
|
// Try new Gecko method
|
|
d.execCommand("styleWithCSS", 0, s);
|
|
} catch (ex) {
|
|
// Use old
|
|
d.execCommand("useCSS", 0, !s);
|
|
}
|
|
};
|
|
|
|
if (isGecko || isOpera) {
|
|
set(true);
|
|
d.execCommand('hilitecolor', false, val);
|
|
set(false);
|
|
} else
|
|
d.execCommand('BackColor', false, val);
|
|
},
|
|
|
|
FormatBlock : function(ui, val) {
|
|
var t = this, ed = t.editor, s = ed.selection, dom = ed.dom, bl, nb, b;
|
|
|
|
function isBlock(n) {
|
|
return /^(P|DIV|H[1-6]|ADDRESS|BLOCKQUOTE|PRE)$/.test(n.nodeName);
|
|
};
|
|
|
|
bl = dom.getParent(s.getNode(), function(n) {
|
|
return isBlock(n);
|
|
});
|
|
|
|
// IE has an issue where it removes the parent div if you change format on the paragrah in <div><p>Content</p></div>
|
|
// FF and Opera doesn't change parent DIV elements if you switch format
|
|
if (bl) {
|
|
if ((isIE && isBlock(bl.parentNode)) || bl.nodeName == 'DIV') {
|
|
// Rename block element
|
|
nb = ed.dom.create(val);
|
|
|
|
each(dom.getAttribs(bl), function(v) {
|
|
dom.setAttrib(nb, v.nodeName, dom.getAttrib(bl, v.nodeName));
|
|
});
|
|
|
|
b = s.getBookmark();
|
|
dom.replace(nb, bl, 1);
|
|
s.moveToBookmark(b);
|
|
ed.nodeChanged();
|
|
return;
|
|
}
|
|
}
|
|
|
|
val = ed.settings.forced_root_block ? (val || '<p>') : val;
|
|
|
|
if (val.indexOf('<') == -1)
|
|
val = '<' + val + '>';
|
|
|
|
if (tinymce.isGecko)
|
|
val = val.replace(/<(div|blockquote|code|dt|dd|dl|samp)>/gi, '$1');
|
|
|
|
ed.getDoc().execCommand('FormatBlock', false, val);
|
|
},
|
|
|
|
mceCleanup : function() {
|
|
var ed = this.editor, s = ed.selection, b = s.getBookmark();
|
|
ed.setContent(ed.getContent());
|
|
s.moveToBookmark(b);
|
|
},
|
|
|
|
mceRemoveNode : function(ui, val) {
|
|
var ed = this.editor, s = ed.selection, b, n = val || s.getNode();
|
|
|
|
// Make sure that the body node isn't removed
|
|
if (n == ed.getBody())
|
|
return;
|
|
|
|
b = s.getBookmark();
|
|
ed.dom.remove(n, 1);
|
|
s.moveToBookmark(b);
|
|
ed.nodeChanged();
|
|
},
|
|
|
|
mceSelectNodeDepth : function(ui, val) {
|
|
var ed = this.editor, s = ed.selection, c = 0;
|
|
|
|
ed.dom.getParent(s.getNode(), function(n) {
|
|
if (n.nodeType == 1 && c++ == val) {
|
|
s.select(n);
|
|
ed.nodeChanged();
|
|
return false;
|
|
}
|
|
}, ed.getBody());
|
|
},
|
|
|
|
mceSelectNode : function(u, v) {
|
|
this.editor.selection.select(v);
|
|
},
|
|
|
|
mceInsertContent : function(ui, val) {
|
|
this.editor.selection.setContent(val);
|
|
},
|
|
|
|
mceInsertRawHTML : function(ui, val) {
|
|
var ed = this.editor;
|
|
|
|
ed.selection.setContent('tiny_mce_marker');
|
|
ed.setContent(ed.getContent().replace(/tiny_mce_marker/g, val));
|
|
},
|
|
|
|
mceRepaint : function() {
|
|
var s, b, e = this.editor;
|
|
|
|
if (tinymce.isGecko) {
|
|
try {
|
|
s = e.selection;
|
|
b = s.getBookmark(true);
|
|
|
|
if (s.getSel())
|
|
s.getSel().selectAllChildren(e.getBody());
|
|
|
|
s.collapse(true);
|
|
s.moveToBookmark(b);
|
|
} catch (ex) {
|
|
// Ignore
|
|
}
|
|
}
|
|
},
|
|
|
|
queryStateUnderline : function() {
|
|
var ed = this.editor, n = ed.selection.getNode();
|
|
|
|
if (n && n.nodeName == 'A')
|
|
return false;
|
|
|
|
return this._queryState('Underline');
|
|
},
|
|
|
|
queryStateOutdent : function() {
|
|
var ed = this.editor, n;
|
|
|
|
if (ed.settings.inline_styles) {
|
|
if ((n = ed.dom.getParent(ed.selection.getStart(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0)
|
|
return true;
|
|
|
|
if ((n = ed.dom.getParent(ed.selection.getEnd(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0)
|
|
return true;
|
|
}
|
|
|
|
return this.queryStateInsertUnorderedList() || this.queryStateInsertOrderedList() || (!ed.settings.inline_styles && !!ed.dom.getParent(ed.selection.getNode(), 'BLOCKQUOTE'));
|
|
},
|
|
|
|
queryStateInsertUnorderedList : function() {
|
|
return this.editor.dom.getParent(this.editor.selection.getNode(), 'UL');
|
|
},
|
|
|
|
queryStateInsertOrderedList : function() {
|
|
return this.editor.dom.getParent(this.editor.selection.getNode(), 'OL');
|
|
},
|
|
|
|
queryStatemceBlockQuote : function() {
|
|
return !!this.editor.dom.getParent(this.editor.selection.getStart(), function(n) {return n.nodeName === 'BLOCKQUOTE';});
|
|
},
|
|
|
|
_applyInlineStyle : function(na, at, op) {
|
|
var t = this, ed = t.editor, dom = ed.dom, bm, lo = {}, kh, found;
|
|
|
|
na = na.toUpperCase();
|
|
|
|
if (op && op.check_classes && at['class'])
|
|
op.check_classes.push(at['class']);
|
|
|
|
function removeEmpty() {
|
|
each(dom.select(na).reverse(), function(n) {
|
|
var c = 0;
|
|
|
|
// Check if there is any attributes
|
|
each(dom.getAttribs(n), function(an) {
|
|
if (an.nodeName.substring(0, 1) != '_' && dom.getAttrib(n, an.nodeName) != '') {
|
|
//console.log(dom.getOuterHTML(n), dom.getAttrib(n, an.nodeName));
|
|
c++;
|
|
}
|
|
});
|
|
|
|
// No attributes then remove the element and keep the children
|
|
if (c == 0)
|
|
dom.remove(n, 1);
|
|
});
|
|
};
|
|
|
|
function replaceFonts() {
|
|
var bm;
|
|
|
|
each(dom.select('span,font'), function(n) {
|
|
if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') {
|
|
if (!bm)
|
|
bm = ed.selection.getBookmark();
|
|
|
|
at._mce_new = '1';
|
|
dom.replace(dom.create(na, at), n, 1);
|
|
}
|
|
});
|
|
|
|
// Remove redundant elements
|
|
each(dom.select(na + '[_mce_new]'), function(n) {
|
|
function removeStyle(n) {
|
|
if (n.nodeType == 1) {
|
|
each(at.style, function(v, k) {
|
|
dom.setStyle(n, k, '');
|
|
});
|
|
|
|
// Remove spans with the same class or marked classes
|
|
if (at['class'] && n.className && op) {
|
|
each(op.check_classes, function(c) {
|
|
if (dom.hasClass(n, c))
|
|
dom.removeClass(n, c);
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// Remove specified style information from child elements
|
|
each(dom.select(na, n), removeStyle);
|
|
|
|
// Remove the specified style information on parent if current node is only child (IE)
|
|
if (n.parentNode && n.parentNode.nodeType == 1 && n.parentNode.childNodes.length == 1)
|
|
removeStyle(n.parentNode);
|
|
|
|
// Remove the child elements style info if a parent already has it
|
|
dom.getParent(n.parentNode, function(pn) {
|
|
if (pn.nodeType == 1) {
|
|
if (at.style) {
|
|
each(at.style, function(v, k) {
|
|
var sv;
|
|
|
|
if (!lo[k] && (sv = dom.getStyle(pn, k))) {
|
|
if (sv === v)
|
|
dom.setStyle(n, k, '');
|
|
|
|
lo[k] = 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Remove spans with the same class or marked classes
|
|
if (at['class'] && pn.className && op) {
|
|
each(op.check_classes, function(c) {
|
|
if (dom.hasClass(pn, c))
|
|
dom.removeClass(n, c);
|
|
});
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
n.removeAttribute('_mce_new');
|
|
});
|
|
|
|
removeEmpty();
|
|
ed.selection.moveToBookmark(bm);
|
|
|
|
return !!bm;
|
|
};
|
|
|
|
// Create inline elements
|
|
ed.focus();
|
|
ed.getDoc().execCommand('FontName', false, 'mceinline');
|
|
replaceFonts();
|
|
|
|
if (kh = t._applyInlineStyle.keyhandler) {
|
|
ed.onKeyUp.remove(kh);
|
|
ed.onKeyPress.remove(kh);
|
|
ed.onKeyDown.remove(kh);
|
|
ed.onSetContent.remove(t._applyInlineStyle.chandler);
|
|
}
|
|
|
|
if (ed.selection.isCollapsed()) {
|
|
// IE will format the current word so this code can't be executed on that browser
|
|
if (!isIE) {
|
|
each(dom.getParents(ed.selection.getNode(), 'span'), function(n) {
|
|
each(at.style, function(v, k) {
|
|
var kv;
|
|
|
|
if (kv = dom.getStyle(n, k)) {
|
|
if (kv == v) {
|
|
dom.setStyle(n, k, '');
|
|
found = 2;
|
|
return false;
|
|
}
|
|
|
|
found = 1;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if (found)
|
|
return false;
|
|
});
|
|
|
|
if (found == 2) {
|
|
bm = ed.selection.getBookmark();
|
|
|
|
removeEmpty();
|
|
|
|
ed.selection.moveToBookmark(bm);
|
|
|
|
// Node change needs to be detached since the onselect event
|
|
// for the select box will run the onclick handler after onselect call. Todo: Add a nicer fix!
|
|
window.setTimeout(function() {
|
|
ed.nodeChanged();
|
|
}, 1);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Start collecting styles
|
|
t._pendingStyles = tinymce.extend(t._pendingStyles || {}, at.style);
|
|
|
|
t._applyInlineStyle.chandler = ed.onSetContent.add(function() {
|
|
delete t._pendingStyles;
|
|
});
|
|
|
|
t._applyInlineStyle.keyhandler = kh = function(e) {
|
|
// Use pending styles
|
|
if (t._pendingStyles) {
|
|
at.style = t._pendingStyles;
|
|
delete t._pendingStyles;
|
|
}
|
|
|
|
if (replaceFonts()) {
|
|
ed.onKeyDown.remove(t._applyInlineStyle.keyhandler);
|
|
ed.onKeyPress.remove(t._applyInlineStyle.keyhandler);
|
|
}
|
|
|
|
if (e.type == 'keyup')
|
|
ed.onKeyUp.remove(t._applyInlineStyle.keyhandler);
|
|
};
|
|
|
|
ed.onKeyDown.add(kh);
|
|
ed.onKeyPress.add(kh);
|
|
ed.onKeyUp.add(kh);
|
|
} else
|
|
t._pendingStyles = 0;
|
|
}
|
|
});
|
|
})(tinymce);(function(tinymce) {
|
|
tinymce.create('tinymce.UndoManager', {
|
|
index : 0,
|
|
data : null,
|
|
typing : 0,
|
|
|
|
UndoManager : function(ed) {
|
|
var t = this, Dispatcher = tinymce.util.Dispatcher;
|
|
|
|
t.editor = ed;
|
|
t.data = [];
|
|
t.onAdd = new Dispatcher(this);
|
|
t.onUndo = new Dispatcher(this);
|
|
t.onRedo = new Dispatcher(this);
|
|
},
|
|
|
|
add : function(l) {
|
|
var t = this, i, ed = t.editor, b, s = ed.settings, la;
|
|
|
|
l = l || {};
|
|
l.content = l.content || ed.getContent({format : 'raw', no_events : 1});
|
|
|
|
// Add undo level if needed
|
|
l.content = l.content.replace(/^\s*|\s*$/g, '');
|
|
la = t.data[t.index > 0 && (t.index == 0 || t.index == t.data.length) ? t.index - 1 : t.index];
|
|
if (!l.initial && la && l.content == la.content)
|
|
return null;
|
|
|
|
// Time to compress
|
|
if (s.custom_undo_redo_levels) {
|
|
if (t.data.length > s.custom_undo_redo_levels) {
|
|
for (i = 0; i < t.data.length - 1; i++)
|
|
t.data[i] = t.data[i + 1];
|
|
|
|
t.data.length--;
|
|
t.index = t.data.length;
|
|
}
|
|
}
|
|
|
|
if (s.custom_undo_redo_restore_selection && !l.initial)
|
|
l.bookmark = b = l.bookmark || ed.selection.getBookmark();
|
|
|
|
if (t.index < t.data.length)
|
|
t.index++;
|
|
|
|
// Only initial marked undo levels should be allowed as first item
|
|
// This to workaround a bug with Firefox and the blur event
|
|
if (t.data.length === 0 && !l.initial)
|
|
return null;
|
|
|
|
// Add level
|
|
t.data.length = t.index + 1;
|
|
t.data[t.index++] = l;
|
|
|
|
if (l.initial)
|
|
t.index = 0;
|
|
|
|
// Set initial bookmark use first real undo level
|
|
if (t.data.length == 2 && t.data[0].initial)
|
|
t.data[0].bookmark = b;
|
|
|
|
t.onAdd.dispatch(t, l);
|
|
ed.isNotDirty = 0;
|
|
|
|
//console.dir(t.data);
|
|
|
|
return l;
|
|
},
|
|
|
|
undo : function() {
|
|
var t = this, ed = t.editor, l = l, i;
|
|
|
|
if (t.typing) {
|
|
t.add();
|
|
t.typing = 0;
|
|
}
|
|
|
|
if (t.index > 0) {
|
|
// If undo on last index then take snapshot
|
|
if (t.index == t.data.length && t.index > 1) {
|
|
i = t.index;
|
|
t.typing = 0;
|
|
|
|
if (!t.add())
|
|
t.index = i;
|
|
|
|
--t.index;
|
|
}
|
|
|
|
l = t.data[--t.index];
|
|
ed.setContent(l.content, {format : 'raw'});
|
|
ed.selection.moveToBookmark(l.bookmark);
|
|
|
|
t.onUndo.dispatch(t, l);
|
|
}
|
|
|
|
return l;
|
|
},
|
|
|
|
redo : function() {
|
|
var t = this, ed = t.editor, l = null;
|
|
|
|
if (t.index < t.data.length - 1) {
|
|
l = t.data[++t.index];
|
|
ed.setContent(l.content, {format : 'raw'});
|
|
ed.selection.moveToBookmark(l.bookmark);
|
|
|
|
t.onRedo.dispatch(t, l);
|
|
}
|
|
|
|
return l;
|
|
},
|
|
|
|
clear : function() {
|
|
var t = this;
|
|
|
|
t.data = [];
|
|
t.index = 0;
|
|
t.typing = 0;
|
|
t.add({initial : true});
|
|
},
|
|
|
|
hasUndo : function() {
|
|
return this.index != 0 || this.typing;
|
|
},
|
|
|
|
hasRedo : function() {
|
|
return this.index < this.data.length - 1;
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
// Shorten names
|
|
var Event, isIE, isGecko, isOpera, each, extend;
|
|
|
|
Event = tinymce.dom.Event;
|
|
isIE = tinymce.isIE;
|
|
isGecko = tinymce.isGecko;
|
|
isOpera = tinymce.isOpera;
|
|
each = tinymce.each;
|
|
extend = tinymce.extend;
|
|
|
|
// Checks if the selection/caret is at the end of the specified block element
|
|
function isAtEnd(rng, par) {
|
|
var rng2 = par.ownerDocument.createRange();
|
|
|
|
rng2.setStart(rng.endContainer, rng.endOffset);
|
|
rng2.setEndAfter(par);
|
|
|
|
// Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
|
|
return rng2.cloneContents().textContent.length == 0;
|
|
};
|
|
|
|
function isEmpty(n) {
|
|
n = n.innerHTML;
|
|
|
|
n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
|
|
n = n.replace(/<[^>]+>/g, ''); // Remove all tags
|
|
|
|
return n.replace(/[ \t\r\n]+/g, '') == '';
|
|
};
|
|
|
|
tinymce.create('tinymce.ForceBlocks', {
|
|
ForceBlocks : function(ed) {
|
|
var t = this, s = ed.settings, elm;
|
|
|
|
t.editor = ed;
|
|
t.dom = ed.dom;
|
|
elm = (s.forced_root_block || 'p').toLowerCase();
|
|
s.element = elm.toUpperCase();
|
|
|
|
ed.onPreInit.add(t.setup, t);
|
|
|
|
t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi');
|
|
t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
|
|
t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
|
|
t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi');
|
|
t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
|
|
|
|
function padd(ed, o) {
|
|
if (isOpera)
|
|
o.content = o.content.replace(t.reOpera, '</' + elm + '>');
|
|
|
|
o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
|
|
|
|
if (!isIE && !isOpera && o.set) {
|
|
// Use instead of BR in padded paragraphs
|
|
o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
|
|
o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
|
|
} else
|
|
o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
|
|
};
|
|
|
|
ed.onBeforeSetContent.add(padd);
|
|
ed.onPostProcess.add(padd);
|
|
|
|
if (s.forced_root_block) {
|
|
ed.onInit.add(t.forceRoots, t);
|
|
ed.onSetContent.add(t.forceRoots, t);
|
|
ed.onBeforeGetContent.add(t.forceRoots, t);
|
|
}
|
|
},
|
|
|
|
setup : function() {
|
|
var t = this, ed = t.editor, s = ed.settings;
|
|
|
|
// Force root blocks when typing and when getting output
|
|
if (s.forced_root_block) {
|
|
ed.onKeyUp.add(t.forceRoots, t);
|
|
ed.onPreProcess.add(t.forceRoots, t);
|
|
}
|
|
|
|
if (s.force_br_newlines) {
|
|
// Force IE to produce BRs on enter
|
|
if (isIE) {
|
|
ed.onKeyPress.add(function(ed, e) {
|
|
var n, s = ed.selection;
|
|
|
|
if (e.keyCode == 13 && s.getNode().nodeName != 'LI') {
|
|
s.setContent('<br id="__" /> ', {format : 'raw'});
|
|
n = ed.dom.get('__');
|
|
n.removeAttribute('id');
|
|
s.select(n);
|
|
s.collapse();
|
|
return Event.cancel(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!isIE && s.force_p_newlines) {
|
|
/* ed.onPreProcess.add(function(ed, o) {
|
|
each(ed.dom.select('br', o.node), function(n) {
|
|
var p = n.parentNode;
|
|
|
|
// Replace <p><br /></p> with <p> </p>
|
|
if (p && p.nodeName == 'p' && (p.childNodes.length == 1 || p.lastChild == n)) {
|
|
p.replaceChild(ed.getDoc().createTextNode('\u00a0'), n);
|
|
}
|
|
});
|
|
});*/
|
|
|
|
ed.onKeyPress.add(function(ed, e) {
|
|
if (e.keyCode == 13 && !e.shiftKey) {
|
|
if (!t.insertPara(e))
|
|
Event.cancel(e);
|
|
}
|
|
});
|
|
|
|
if (isGecko) {
|
|
ed.onKeyDown.add(function(ed, e) {
|
|
if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
|
|
t.backspaceDelete(e, e.keyCode == 8);
|
|
});
|
|
}
|
|
}
|
|
|
|
function ren(rn, na) {
|
|
var ne = ed.dom.create(na);
|
|
|
|
each(rn.attributes, function(a) {
|
|
if (a.specified && a.nodeValue)
|
|
ne.setAttribute(a.nodeName.toLowerCase(), a.nodeValue);
|
|
});
|
|
|
|
each(rn.childNodes, function(n) {
|
|
ne.appendChild(n.cloneNode(true));
|
|
});
|
|
|
|
rn.parentNode.replaceChild(ne, rn);
|
|
|
|
return ne;
|
|
};
|
|
|
|
// Padd empty inline elements within block elements
|
|
// For example: <p><strong><em></em></strong></p> becomes <p><strong><em> </em></strong></p>
|
|
ed.onPreProcess.add(function(ed, o) {
|
|
each(ed.dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
|
|
if (isEmpty(p)) {
|
|
each(ed.dom.select('span,em,strong,b,i', o.node), function(n) {
|
|
if (!n.hasChildNodes()) {
|
|
n.appendChild(ed.getDoc().createTextNode('\u00a0'));
|
|
return false; // Break the loop one padding is enough
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// IE specific fixes
|
|
if (isIE) {
|
|
// Replaces IE:s auto generated paragraphs with the specified element name
|
|
if (s.element != 'P') {
|
|
ed.onKeyPress.add(function(ed, e) {
|
|
t.lastElm = ed.selection.getNode().nodeName;
|
|
});
|
|
|
|
ed.onKeyUp.add(function(ed, e) {
|
|
var bl, sel = ed.selection, n = sel.getNode(), b = ed.getBody();
|
|
|
|
if (b.childNodes.length === 1 && n.nodeName == 'P') {
|
|
n = ren(n, s.element);
|
|
sel.select(n);
|
|
sel.collapse();
|
|
ed.nodeChanged();
|
|
} else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
|
|
bl = ed.dom.getParent(n, 'p');
|
|
|
|
if (bl) {
|
|
ren(bl, s.element);
|
|
ed.nodeChanged();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
find : function(n, t, s) {
|
|
var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, false), c = -1;
|
|
|
|
while (n = w.nextNode()) {
|
|
c++;
|
|
|
|
// Index by node
|
|
if (t == 0 && n == s)
|
|
return c;
|
|
|
|
// Node by index
|
|
if (t == 1 && c == s)
|
|
return n;
|
|
}
|
|
|
|
return -1;
|
|
},
|
|
|
|
forceRoots : function(ed, e) {
|
|
var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
|
|
var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
|
|
|
|
// Fix for bug #1863847
|
|
//if (e && e.keyCode == 13)
|
|
// return true;
|
|
|
|
// Wrap non blocks into blocks
|
|
for (i = nl.length - 1; i >= 0; i--) {
|
|
nx = nl[i];
|
|
|
|
// Is text or non block element
|
|
if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
|
|
if (!bl) {
|
|
// Create new block but ignore whitespace
|
|
if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
|
|
// Store selection
|
|
if (si == -2 && r) {
|
|
if (!isIE) {
|
|
// If selection is element then mark it
|
|
if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
|
|
// Save the id of the selected element
|
|
eid = n.getAttribute("id");
|
|
n.setAttribute("id", "__mce");
|
|
} else {
|
|
// If element is inside body, might not be the case in contentEdiable mode
|
|
if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
|
|
so = r.startOffset;
|
|
eo = r.endOffset;
|
|
si = t.find(b, 0, r.startContainer);
|
|
ei = t.find(b, 0, r.endContainer);
|
|
}
|
|
}
|
|
} else {
|
|
tr = d.body.createTextRange();
|
|
tr.moveToElementText(b);
|
|
tr.collapse(1);
|
|
bp = tr.move('character', c) * -1;
|
|
|
|
tr = r.duplicate();
|
|
tr.collapse(1);
|
|
sp = tr.move('character', c) * -1;
|
|
|
|
tr = r.duplicate();
|
|
tr.collapse(0);
|
|
le = (tr.move('character', c) * -1) - sp;
|
|
|
|
si = sp - bp;
|
|
ei = le;
|
|
}
|
|
}
|
|
|
|
// Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
|
|
// See: http://support.microsoft.com/kb/829907
|
|
bl = ed.dom.create(ed.settings.forced_root_block);
|
|
nx.parentNode.replaceChild(bl, nx);
|
|
bl.appendChild(nx);
|
|
}
|
|
} else {
|
|
if (bl.hasChildNodes())
|
|
bl.insertBefore(nx, bl.firstChild);
|
|
else
|
|
bl.appendChild(nx);
|
|
}
|
|
} else
|
|
bl = null; // Time to create new block
|
|
}
|
|
|
|
// Restore selection
|
|
if (si != -2) {
|
|
if (!isIE) {
|
|
bl = b.getElementsByTagName(ed.settings.element)[0];
|
|
r = d.createRange();
|
|
|
|
// Select last location or generated block
|
|
if (si != -1)
|
|
r.setStart(t.find(b, 1, si), so);
|
|
else
|
|
r.setStart(bl, 0);
|
|
|
|
// Select last location or generated block
|
|
if (ei != -1)
|
|
r.setEnd(t.find(b, 1, ei), eo);
|
|
else
|
|
r.setEnd(bl, 0);
|
|
|
|
if (s) {
|
|
s.removeAllRanges();
|
|
s.addRange(r);
|
|
}
|
|
} else {
|
|
try {
|
|
r = s.createRange();
|
|
r.moveToElementText(b);
|
|
r.collapse(1);
|
|
r.moveStart('character', si);
|
|
r.moveEnd('character', ei);
|
|
r.select();
|
|
} catch (ex) {
|
|
// Ignore
|
|
}
|
|
}
|
|
} else if (!isIE && (n = ed.dom.get('__mce'))) {
|
|
// Restore the id of the selected element
|
|
if (eid)
|
|
n.setAttribute('id', eid);
|
|
else
|
|
n.removeAttribute('id');
|
|
|
|
// Move caret before selected element
|
|
r = d.createRange();
|
|
r.setStartBefore(n);
|
|
r.setEndBefore(n);
|
|
se.setRng(r);
|
|
}
|
|
},
|
|
|
|
getParentBlock : function(n) {
|
|
var d = this.dom;
|
|
|
|
return d.getParent(n, d.isBlock);
|
|
},
|
|
|
|
insertPara : function(e) {
|
|
var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
|
|
var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
|
|
|
|
// If root blocks are forced then use Operas default behavior since it's really good
|
|
// Removed due to bug: #1853816
|
|
// if (se.forced_root_block && isOpera)
|
|
// return true;
|
|
|
|
// Setup before range
|
|
rb = d.createRange();
|
|
|
|
// If is before the first block element and in body, then move it into first block element
|
|
rb.setStart(s.anchorNode, s.anchorOffset);
|
|
rb.collapse(true);
|
|
|
|
// Setup after range
|
|
ra = d.createRange();
|
|
|
|
// If is before the first block element and in body, then move it into first block element
|
|
ra.setStart(s.focusNode, s.focusOffset);
|
|
ra.collapse(true);
|
|
|
|
// Setup start/end points
|
|
dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
|
|
sn = dir ? s.anchorNode : s.focusNode;
|
|
so = dir ? s.anchorOffset : s.focusOffset;
|
|
en = dir ? s.focusNode : s.anchorNode;
|
|
eo = dir ? s.focusOffset : s.anchorOffset;
|
|
|
|
// If selection is in empty table cell
|
|
if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
|
|
if (sn.firstChild.nodeName == 'BR')
|
|
dom.remove(sn.firstChild); // Remove BR
|
|
|
|
// Create two new block elements
|
|
if (sn.childNodes.length == 0) {
|
|
ed.dom.add(sn, se.element, null, '<br />');
|
|
aft = ed.dom.add(sn, se.element, null, '<br />');
|
|
} else {
|
|
n = sn.innerHTML;
|
|
sn.innerHTML = '';
|
|
ed.dom.add(sn, se.element, null, n);
|
|
aft = ed.dom.add(sn, se.element, null, '<br />');
|
|
}
|
|
|
|
// Move caret into the last one
|
|
r = d.createRange();
|
|
r.selectNodeContents(aft);
|
|
r.collapse(1);
|
|
ed.selection.setRng(r);
|
|
|
|
return false;
|
|
}
|
|
|
|
// If the caret is in an invalid location in FF we need to move it into the first block
|
|
if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
|
|
sn = en = sn.firstChild;
|
|
so = eo = 0;
|
|
rb = d.createRange();
|
|
rb.setStart(sn, 0);
|
|
ra = d.createRange();
|
|
ra.setStart(en, 0);
|
|
}
|
|
|
|
// Never use body as start or end node
|
|
sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
|
|
sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
|
|
en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
|
|
en = en.nodeName == "BODY" ? en.firstChild : en;
|
|
|
|
// Get start and end blocks
|
|
sb = t.getParentBlock(sn);
|
|
eb = t.getParentBlock(en);
|
|
bn = sb ? sb.nodeName : se.element; // Get block name to create
|
|
|
|
// Return inside list use default browser behavior
|
|
if (t.dom.getParent(sb, 'ol,ul,pre'))
|
|
return true;
|
|
|
|
// If caption or absolute layers then always generate new blocks within
|
|
if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
|
|
bn = se.element;
|
|
sb = null;
|
|
}
|
|
|
|
// If caption or absolute layers then always generate new blocks within
|
|
if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
|
|
bn = se.element;
|
|
eb = null;
|
|
}
|
|
|
|
// Use P instead
|
|
if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
|
|
bn = se.element;
|
|
sb = eb = null;
|
|
}
|
|
|
|
// Setup new before and after blocks
|
|
bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
|
|
aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
|
|
|
|
// Remove id from after clone
|
|
aft.removeAttribute('id');
|
|
|
|
// Is header and cursor is at the end, then force paragraph under
|
|
if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
|
|
aft = ed.dom.create(se.element);
|
|
|
|
// Find start chop node
|
|
n = sc = sn;
|
|
do {
|
|
if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
|
|
break;
|
|
|
|
sc = n;
|
|
} while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
|
|
|
|
// Find end chop node
|
|
n = ec = en;
|
|
do {
|
|
if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
|
|
break;
|
|
|
|
ec = n;
|
|
} while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
|
|
|
|
// Place first chop part into before block element
|
|
if (sc.nodeName == bn)
|
|
rb.setStart(sc, 0);
|
|
else
|
|
rb.setStartBefore(sc);
|
|
|
|
rb.setEnd(sn, so);
|
|
bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
|
|
|
|
// Place secnd chop part within new block element
|
|
try {
|
|
ra.setEndAfter(ec);
|
|
} catch(ex) {
|
|
//console.debug(s.focusNode, s.focusOffset);
|
|
}
|
|
|
|
ra.setStart(en, eo);
|
|
aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
|
|
|
|
// Create range around everything
|
|
r = d.createRange();
|
|
if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
|
|
r.setStartBefore(sc.parentNode);
|
|
} else {
|
|
if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
|
|
r.setStartBefore(rb.startContainer);
|
|
else
|
|
r.setStart(rb.startContainer, rb.startOffset);
|
|
}
|
|
|
|
if (!ec.nextSibling && ec.parentNode.nodeName == bn)
|
|
r.setEndAfter(ec.parentNode);
|
|
else
|
|
r.setEnd(ra.endContainer, ra.endOffset);
|
|
|
|
// Delete and replace it with new block elements
|
|
r.deleteContents();
|
|
|
|
if (isOpera)
|
|
ed.getWin().scrollTo(0, vp.y);
|
|
|
|
// Never wrap blocks in blocks
|
|
if (bef.firstChild && bef.firstChild.nodeName == bn)
|
|
bef.innerHTML = bef.firstChild.innerHTML;
|
|
|
|
if (aft.firstChild && aft.firstChild.nodeName == bn)
|
|
aft.innerHTML = aft.firstChild.innerHTML;
|
|
|
|
// Padd empty blocks
|
|
if (isEmpty(bef))
|
|
bef.innerHTML = '<br />';
|
|
|
|
function appendStyles(e, en) {
|
|
var nl = [], nn, n, i;
|
|
|
|
e.innerHTML = '';
|
|
|
|
// Make clones of style elements
|
|
if (se.keep_styles) {
|
|
n = en;
|
|
do {
|
|
// We only want style specific elements
|
|
if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
|
|
nn = n.cloneNode(false);
|
|
dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
|
|
nl.push(nn);
|
|
}
|
|
} while (n = n.parentNode);
|
|
}
|
|
|
|
// Append style elements to aft
|
|
if (nl.length > 0) {
|
|
for (i = nl.length - 1, nn = e; i >= 0; i--)
|
|
nn = nn.appendChild(nl[i]);
|
|
|
|
// Padd most inner style element
|
|
nl[0].innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
|
|
return nl[0]; // Move caret to most inner element
|
|
} else
|
|
e.innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
|
|
};
|
|
|
|
// Fill empty afterblook with current style
|
|
if (isEmpty(aft))
|
|
car = appendStyles(aft, en);
|
|
|
|
// Opera needs this one backwards for older versions
|
|
if (isOpera && parseFloat(opera.version()) < 9.5) {
|
|
r.insertNode(bef);
|
|
r.insertNode(aft);
|
|
} else {
|
|
r.insertNode(aft);
|
|
r.insertNode(bef);
|
|
}
|
|
|
|
// Normalize
|
|
aft.normalize();
|
|
bef.normalize();
|
|
|
|
function first(n) {
|
|
return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false).nextNode() || n;
|
|
};
|
|
|
|
// Move cursor and scroll into view
|
|
r = d.createRange();
|
|
r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
|
|
r.collapse(1);
|
|
s.removeAllRanges();
|
|
s.addRange(r);
|
|
|
|
// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
|
|
y = ed.dom.getPos(aft).y;
|
|
ch = aft.clientHeight;
|
|
|
|
// Is element within viewport
|
|
if (y < vp.y || y + ch > vp.y + vp.h) {
|
|
ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
|
|
//console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight));
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
backspaceDelete : function(e, bs) {
|
|
var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn;
|
|
|
|
/*
|
|
var par, rng, nextBlock;
|
|
|
|
// Delete key will not merge paragraphs on Gecko so we need to do this manually
|
|
// Hitting the delete key at the following caret position doesn't merge the elements <p>A|</p><p>B</p>
|
|
// This logic will merge them into this: <p>A|B</p>
|
|
if (e.keyCode == 46) {
|
|
if (r.collapsed) {
|
|
par = dom.getParent(sc, 'p,h1,h2,h3,h4,h5,h6,div');
|
|
|
|
if (par) {
|
|
rng = dom.createRng();
|
|
|
|
rng.setStart(sc, r.startOffset);
|
|
rng.setEndAfter(par);
|
|
|
|
// Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
|
|
if (dom.getOuterHTML(rng.cloneContents()).replace(/<[^>]+>/g, '').length == 0) {
|
|
nextBlock = dom.getNext(par, 'p,h1,h2,h3,h4,h5,h6,div');
|
|
|
|
// Copy all children from next sibling block and remove it
|
|
if (nextBlock) {
|
|
each(nextBlock.childNodes, function(node) {
|
|
par.appendChild(node.cloneNode(true));
|
|
});
|
|
|
|
dom.remove(nextBlock);
|
|
}
|
|
|
|
// Block the default even since the Gecko team might eventually fix this
|
|
// We will remove this logic once they do we can't feature detect this one
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
// The caret sometimes gets stuck in Gecko if you delete empty paragraphs
|
|
// This workaround removes the element by hand and moves the caret to the previous element
|
|
if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
|
|
if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
|
|
// Find previous block element
|
|
n = sc;
|
|
while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
|
|
|
|
if (n) {
|
|
if (sc != b.firstChild) {
|
|
// Find last text node
|
|
w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
|
|
while (tn = w.nextNode())
|
|
n = tn;
|
|
|
|
// Place caret at the end of last text node
|
|
r = ed.getDoc().createRange();
|
|
r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
|
|
r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
|
|
se.setRng(r);
|
|
|
|
// Remove the target container
|
|
ed.dom.remove(sc);
|
|
}
|
|
|
|
return Event.cancel(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gecko generates BR elements here and there, we don't like those so lets remove them
|
|
function handler(e) {
|
|
var pr;
|
|
|
|
e = e.target;
|
|
|
|
// A new BR was created in a block element, remove it
|
|
if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) {
|
|
pr = e.previousSibling;
|
|
|
|
Event.remove(b, 'DOMNodeInserted', handler);
|
|
|
|
// Is there whitespace at the end of the node before then we might need the pesky BR
|
|
// to place the caret at a correct location see bug: #2013943
|
|
if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue))
|
|
return;
|
|
|
|
// Only remove BR elements that got inserted in the middle of the text
|
|
if (e.previousSibling || e.nextSibling)
|
|
ed.dom.remove(e);
|
|
}
|
|
};
|
|
|
|
// Listen for new nodes
|
|
Event._add(b, 'DOMNodeInserted', handler);
|
|
|
|
// Remove listener
|
|
window.setTimeout(function() {
|
|
Event._remove(b, 'DOMNodeInserted', handler);
|
|
}, 1);
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
// Shorten names
|
|
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
|
|
|
|
tinymce.create('tinymce.ControlManager', {
|
|
ControlManager : function(ed, s) {
|
|
var t = this, i;
|
|
|
|
s = s || {};
|
|
t.editor = ed;
|
|
t.controls = {};
|
|
t.onAdd = new tinymce.util.Dispatcher(t);
|
|
t.onPostRender = new tinymce.util.Dispatcher(t);
|
|
t.prefix = s.prefix || ed.id + '_';
|
|
t._cls = {};
|
|
|
|
t.onPostRender.add(function() {
|
|
each(t.controls, function(c) {
|
|
c.postRender();
|
|
});
|
|
});
|
|
},
|
|
|
|
get : function(id) {
|
|
return this.controls[this.prefix + id] || this.controls[id];
|
|
},
|
|
|
|
setActive : function(id, s) {
|
|
var c = null;
|
|
|
|
if (c = this.get(id))
|
|
c.setActive(s);
|
|
|
|
return c;
|
|
},
|
|
|
|
setDisabled : function(id, s) {
|
|
var c = null;
|
|
|
|
if (c = this.get(id))
|
|
c.setDisabled(s);
|
|
|
|
return c;
|
|
},
|
|
|
|
add : function(c) {
|
|
var t = this;
|
|
|
|
if (c) {
|
|
t.controls[c.id] = c;
|
|
t.onAdd.dispatch(c, t);
|
|
}
|
|
|
|
return c;
|
|
},
|
|
|
|
createControl : function(n) {
|
|
var c, t = this, ed = t.editor;
|
|
|
|
each(ed.plugins, function(p) {
|
|
if (p.createControl) {
|
|
c = p.createControl(n, t);
|
|
|
|
if (c)
|
|
return false;
|
|
}
|
|
});
|
|
|
|
switch (n) {
|
|
case "|":
|
|
case "separator":
|
|
return t.createSeparator();
|
|
}
|
|
|
|
if (!c && ed.buttons && (c = ed.buttons[n]))
|
|
return t.createButton(n, c);
|
|
|
|
return t.add(c);
|
|
},
|
|
|
|
createDropMenu : function(id, s, cc) {
|
|
var t = this, ed = t.editor, c, bm, v, cls;
|
|
|
|
s = extend({
|
|
'class' : 'mceDropDown',
|
|
constrain : ed.settings.constrain_menus
|
|
}, s);
|
|
|
|
s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
|
|
if (v = ed.getParam('skin_variant'))
|
|
s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
|
|
|
|
id = t.prefix + id;
|
|
cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
|
|
c = t.controls[id] = new cls(id, s);
|
|
c.onAddItem.add(function(c, o) {
|
|
var s = o.settings;
|
|
|
|
s.title = ed.getLang(s.title, s.title);
|
|
|
|
if (!s.onclick) {
|
|
s.onclick = function(v) {
|
|
ed.execCommand(s.cmd, s.ui || false, s.value);
|
|
};
|
|
}
|
|
});
|
|
|
|
ed.onRemove.add(function() {
|
|
c.destroy();
|
|
});
|
|
|
|
// Fix for bug #1897785, #1898007
|
|
if (tinymce.isIE) {
|
|
c.onShowMenu.add(function() {
|
|
// IE 8 needs focus in order to store away a range with the current collapsed caret location
|
|
ed.focus();
|
|
|
|
bm = ed.selection.getBookmark(1);
|
|
});
|
|
|
|
c.onHideMenu.add(function() {
|
|
if (bm) {
|
|
ed.selection.moveToBookmark(bm);
|
|
bm = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
return t.add(c);
|
|
},
|
|
|
|
createListBox : function(id, s, cc) {
|
|
var t = this, ed = t.editor, cmd, c, cls;
|
|
|
|
if (t.get(id))
|
|
return null;
|
|
|
|
s.title = ed.translate(s.title);
|
|
s.scope = s.scope || ed;
|
|
|
|
if (!s.onselect) {
|
|
s.onselect = function(v) {
|
|
ed.execCommand(s.cmd, s.ui || false, v || s.value);
|
|
};
|
|
}
|
|
|
|
s = extend({
|
|
title : s.title,
|
|
'class' : 'mce_' + id,
|
|
scope : s.scope,
|
|
control_manager : t
|
|
}, s);
|
|
|
|
id = t.prefix + id;
|
|
|
|
if (ed.settings.use_native_selects)
|
|
c = new tinymce.ui.NativeListBox(id, s);
|
|
else {
|
|
cls = cc || t._cls.listbox || tinymce.ui.ListBox;
|
|
c = new cls(id, s);
|
|
}
|
|
|
|
t.controls[id] = c;
|
|
|
|
// Fix focus problem in Safari
|
|
if (tinymce.isWebKit) {
|
|
c.onPostRender.add(function(c, n) {
|
|
// Store bookmark on mousedown
|
|
Event.add(n, 'mousedown', function() {
|
|
ed.bookmark = ed.selection.getBookmark(1);
|
|
});
|
|
|
|
// Restore on focus, since it might be lost
|
|
Event.add(n, 'focus', function() {
|
|
ed.selection.moveToBookmark(ed.bookmark);
|
|
ed.bookmark = null;
|
|
});
|
|
});
|
|
}
|
|
|
|
if (c.hideMenu)
|
|
ed.onMouseDown.add(c.hideMenu, c);
|
|
|
|
return t.add(c);
|
|
},
|
|
|
|
createButton : function(id, s, cc) {
|
|
var t = this, ed = t.editor, o, c, cls;
|
|
|
|
if (t.get(id))
|
|
return null;
|
|
|
|
s.title = ed.translate(s.title);
|
|
s.label = ed.translate(s.label);
|
|
s.scope = s.scope || ed;
|
|
|
|
if (!s.onclick && !s.menu_button) {
|
|
s.onclick = function() {
|
|
ed.execCommand(s.cmd, s.ui || false, s.value);
|
|
};
|
|
}
|
|
|
|
s = extend({
|
|
title : s.title,
|
|
'class' : 'mce_' + id,
|
|
unavailable_prefix : ed.getLang('unavailable', ''),
|
|
scope : s.scope,
|
|
control_manager : t
|
|
}, s);
|
|
|
|
id = t.prefix + id;
|
|
|
|
if (s.menu_button) {
|
|
cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
|
|
c = new cls(id, s);
|
|
ed.onMouseDown.add(c.hideMenu, c);
|
|
} else {
|
|
cls = t._cls.button || tinymce.ui.Button;
|
|
c = new cls(id, s);
|
|
}
|
|
|
|
return t.add(c);
|
|
},
|
|
|
|
createMenuButton : function(id, s, cc) {
|
|
s = s || {};
|
|
s.menu_button = 1;
|
|
|
|
return this.createButton(id, s, cc);
|
|
},
|
|
|
|
createSplitButton : function(id, s, cc) {
|
|
var t = this, ed = t.editor, cmd, c, cls;
|
|
|
|
if (t.get(id))
|
|
return null;
|
|
|
|
s.title = ed.translate(s.title);
|
|
s.scope = s.scope || ed;
|
|
|
|
if (!s.onclick) {
|
|
s.onclick = function(v) {
|
|
ed.execCommand(s.cmd, s.ui || false, v || s.value);
|
|
};
|
|
}
|
|
|
|
if (!s.onselect) {
|
|
s.onselect = function(v) {
|
|
ed.execCommand(s.cmd, s.ui || false, v || s.value);
|
|
};
|
|
}
|
|
|
|
s = extend({
|
|
title : s.title,
|
|
'class' : 'mce_' + id,
|
|
scope : s.scope,
|
|
control_manager : t
|
|
}, s);
|
|
|
|
id = t.prefix + id;
|
|
cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
|
|
c = t.add(new cls(id, s));
|
|
ed.onMouseDown.add(c.hideMenu, c);
|
|
|
|
return c;
|
|
},
|
|
|
|
createColorSplitButton : function(id, s, cc) {
|
|
var t = this, ed = t.editor, cmd, c, cls, bm;
|
|
|
|
if (t.get(id))
|
|
return null;
|
|
|
|
s.title = ed.translate(s.title);
|
|
s.scope = s.scope || ed;
|
|
|
|
if (!s.onclick) {
|
|
s.onclick = function(v) {
|
|
if (tinymce.isIE)
|
|
bm = ed.selection.getBookmark(1);
|
|
|
|
ed.execCommand(s.cmd, s.ui || false, v || s.value);
|
|
};
|
|
}
|
|
|
|
if (!s.onselect) {
|
|
s.onselect = function(v) {
|
|
ed.execCommand(s.cmd, s.ui || false, v || s.value);
|
|
};
|
|
}
|
|
|
|
s = extend({
|
|
title : s.title,
|
|
'class' : 'mce_' + id,
|
|
'menu_class' : ed.getParam('skin') + 'Skin',
|
|
scope : s.scope,
|
|
more_colors_title : ed.getLang('more_colors')
|
|
}, s);
|
|
|
|
id = t.prefix + id;
|
|
cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
|
|
c = new cls(id, s);
|
|
ed.onMouseDown.add(c.hideMenu, c);
|
|
|
|
// Remove the menu element when the editor is removed
|
|
ed.onRemove.add(function() {
|
|
c.destroy();
|
|
});
|
|
|
|
// Fix for bug #1897785, #1898007
|
|
if (tinymce.isIE) {
|
|
c.onShowMenu.add(function() {
|
|
// IE 8 needs focus in order to store away a range with the current collapsed caret location
|
|
ed.focus();
|
|
bm = ed.selection.getBookmark(1);
|
|
});
|
|
|
|
c.onHideMenu.add(function() {
|
|
if (bm) {
|
|
ed.selection.moveToBookmark(bm);
|
|
bm = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
return t.add(c);
|
|
},
|
|
|
|
createToolbar : function(id, s, cc) {
|
|
var c, t = this, cls;
|
|
|
|
id = t.prefix + id;
|
|
cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
|
|
c = new cls(id, s);
|
|
|
|
if (t.get(id))
|
|
return null;
|
|
|
|
return t.add(c);
|
|
},
|
|
|
|
createSeparator : function(cc) {
|
|
var cls = cc || this._cls.separator || tinymce.ui.Separator;
|
|
|
|
return new cls();
|
|
},
|
|
|
|
setControlType : function(n, c) {
|
|
return this._cls[n.toLowerCase()] = c;
|
|
},
|
|
|
|
destroy : function() {
|
|
each(this.controls, function(c) {
|
|
c.destroy();
|
|
});
|
|
|
|
this.controls = null;
|
|
}
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
|
|
|
|
tinymce.create('tinymce.WindowManager', {
|
|
WindowManager : function(ed) {
|
|
var t = this;
|
|
|
|
t.editor = ed;
|
|
t.onOpen = new Dispatcher(t);
|
|
t.onClose = new Dispatcher(t);
|
|
t.params = {};
|
|
t.features = {};
|
|
},
|
|
|
|
open : function(s, p) {
|
|
var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
|
|
|
|
// Default some options
|
|
s = s || {};
|
|
p = p || {};
|
|
sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
|
|
sh = isOpera ? vp.h : screen.height;
|
|
s.name = s.name || 'mc_' + new Date().getTime();
|
|
s.width = parseInt(s.width || 320);
|
|
s.height = parseInt(s.height || 240);
|
|
s.resizable = true;
|
|
s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
|
|
s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
|
|
p.inline = false;
|
|
p.mce_width = s.width;
|
|
p.mce_height = s.height;
|
|
p.mce_auto_focus = s.auto_focus;
|
|
|
|
if (mo) {
|
|
if (isIE) {
|
|
s.center = true;
|
|
s.help = false;
|
|
s.dialogWidth = s.width + 'px';
|
|
s.dialogHeight = s.height + 'px';
|
|
s.scroll = s.scrollbars || false;
|
|
}
|
|
}
|
|
|
|
// Build features string
|
|
each(s, function(v, k) {
|
|
if (tinymce.is(v, 'boolean'))
|
|
v = v ? 'yes' : 'no';
|
|
|
|
if (!/^(name|url)$/.test(k)) {
|
|
if (isIE && mo)
|
|
f += (f ? ';' : '') + k + ':' + v;
|
|
else
|
|
f += (f ? ',' : '') + k + '=' + v;
|
|
}
|
|
});
|
|
|
|
t.features = s;
|
|
t.params = p;
|
|
t.onOpen.dispatch(t, s, p);
|
|
|
|
u = s.url || s.file;
|
|
u = tinymce._addVer(u);
|
|
|
|
try {
|
|
if (isIE && mo) {
|
|
w = 1;
|
|
window.showModalDialog(u, window, f);
|
|
} else
|
|
w = window.open(u, s.name, f);
|
|
} catch (ex) {
|
|
// Ignore
|
|
}
|
|
|
|
if (!w)
|
|
alert(t.editor.getLang('popup_blocked'));
|
|
},
|
|
|
|
close : function(w) {
|
|
w.close();
|
|
this.onClose.dispatch(this);
|
|
},
|
|
|
|
createInstance : function(cl, a, b, c, d, e) {
|
|
var f = tinymce.resolve(cl);
|
|
|
|
return new f(a, b, c, d, e);
|
|
},
|
|
|
|
confirm : function(t, cb, s, w) {
|
|
w = w || window;
|
|
|
|
cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
|
|
},
|
|
|
|
alert : function(tx, cb, s, w) {
|
|
var t = this;
|
|
|
|
w = w || window;
|
|
w.alert(t._decode(t.editor.getLang(tx, tx)));
|
|
|
|
if (cb)
|
|
cb.call(s || t);
|
|
},
|
|
|
|
// Internal functions
|
|
|
|
_decode : function(s) {
|
|
return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
|
|
}
|
|
});
|
|
}(tinymce));(function(tinymce) {
|
|
tinymce.CommandManager = function() {
|
|
var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
|
|
|
|
function add(collection, cmd, func, scope) {
|
|
if (typeof(cmd) == 'string')
|
|
cmd = [cmd];
|
|
|
|
tinymce.each(cmd, function(cmd) {
|
|
collection[cmd.toLowerCase()] = {func : func, scope : scope};
|
|
});
|
|
};
|
|
|
|
tinymce.extend(this, {
|
|
add : function(cmd, func, scope) {
|
|
add(execCommands, cmd, func, scope);
|
|
},
|
|
|
|
addQueryStateHandler : function(cmd, func, scope) {
|
|
add(queryStateCommands, cmd, func, scope);
|
|
},
|
|
|
|
addQueryValueHandler : function(cmd, func, scope) {
|
|
add(queryValueCommands, cmd, func, scope);
|
|
},
|
|
|
|
execCommand : function(scope, cmd, ui, value, args) {
|
|
if (cmd = execCommands[cmd.toLowerCase()]) {
|
|
if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
|
|
return true;
|
|
}
|
|
},
|
|
|
|
queryCommandValue : function() {
|
|
if (cmd = queryValueCommands[cmd.toLowerCase()])
|
|
return cmd.func.call(scope || cmd.scope, ui, value, args);
|
|
},
|
|
|
|
queryCommandState : function() {
|
|
if (cmd = queryStateCommands[cmd.toLowerCase()])
|
|
return cmd.func.call(scope || cmd.scope, ui, value, args);
|
|
}
|
|
});
|
|
};
|
|
|
|
tinymce.GlobalCommands = new tinymce.CommandManager();
|
|
})(tinymce);(function(tinymce) {
|
|
function processRange(dom, start, end, callback) {
|
|
var ancestor, n, startPoint, endPoint, sib;
|
|
|
|
function findEndPoint(n, c) {
|
|
do {
|
|
if (n.parentNode == c)
|
|
return n;
|
|
|
|
n = n.parentNode;
|
|
} while(n);
|
|
};
|
|
|
|
function process(n) {
|
|
callback(n);
|
|
tinymce.walk(n, callback, 'childNodes');
|
|
};
|
|
|
|
// Find common ancestor and end points
|
|
ancestor = dom.findCommonAncestor(start, end);
|
|
startPoint = findEndPoint(start, ancestor) || start;
|
|
endPoint = findEndPoint(end, ancestor) || end;
|
|
|
|
// Process left leaf
|
|
for (n = start; n && n != startPoint; n = n.parentNode) {
|
|
for (sib = n.nextSibling; sib; sib = sib.nextSibling)
|
|
process(sib);
|
|
}
|
|
|
|
// Process middle from start to end point
|
|
if (startPoint != endPoint) {
|
|
for (n = startPoint.nextSibling; n && n != endPoint; n = n.nextSibling)
|
|
process(n);
|
|
} else
|
|
process(startPoint);
|
|
|
|
// Process right leaf
|
|
for (n = end; n && n != endPoint; n = n.parentNode) {
|
|
for (sib = n.previousSibling; sib; sib = sib.previousSibling)
|
|
process(sib);
|
|
}
|
|
};
|
|
|
|
tinymce.GlobalCommands.add('RemoveFormat', function() {
|
|
var ed = this, dom = ed.dom, s = ed.selection, r = s.getRng(1), nodes = [], bm, start, end, sc, so, ec, eo, n;
|
|
|
|
function findFormatRoot(n) {
|
|
var sp;
|
|
|
|
dom.getParent(n, function(n) {
|
|
if (dom.is(n, ed.getParam('removeformat_selector')))
|
|
sp = n;
|
|
|
|
return dom.isBlock(n);
|
|
}, ed.getBody());
|
|
|
|
return sp;
|
|
};
|
|
|
|
function collect(n) {
|
|
if (dom.is(n, ed.getParam('removeformat_selector')))
|
|
nodes.push(n);
|
|
};
|
|
|
|
function walk(n) {
|
|
collect(n);
|
|
tinymce.walk(n, collect, 'childNodes');
|
|
};
|
|
|
|
bm = s.getBookmark();
|
|
sc = r.startContainer;
|
|
ec = r.endContainer;
|
|
so = r.startOffset;
|
|
eo = r.endOffset;
|
|
sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc;
|
|
ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec;
|
|
|
|
// Same container
|
|
if (sc == ec) { // TEXT_NODE
|
|
start = findFormatRoot(sc);
|
|
|
|
// Handle single text node
|
|
if (sc.nodeType == 3) {
|
|
if (start && start.nodeType == 1) { // ELEMENT
|
|
n = sc.splitText(so);
|
|
n.splitText(eo - so);
|
|
dom.split(start, n);
|
|
|
|
s.moveToBookmark(bm);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Handle single element
|
|
walk(dom.split(start, sc) || sc);
|
|
} else {
|
|
// Find start/end format root
|
|
start = findFormatRoot(sc);
|
|
end = findFormatRoot(ec);
|
|
|
|
// Split start text node
|
|
if (start) {
|
|
if (sc.nodeType == 3) { // TEXT
|
|
// Since IE doesn't support white space nodes in the DOM we need to
|
|
// add this invisible character so that the splitText function can split the contents
|
|
if (so == sc.nodeValue.length)
|
|
sc.nodeValue += '\uFEFF'; // Yet another pesky IE fix
|
|
|
|
sc = sc.splitText(so);
|
|
}
|
|
}
|
|
|
|
// Split end text node
|
|
if (end) {
|
|
if (ec.nodeType == 3) // TEXT
|
|
ec.splitText(eo);
|
|
}
|
|
|
|
// If the start and end format root is the same then we need to wrap
|
|
// the end node in a span since the split calls might change the reference
|
|
// Example: <p><b><em>x[yz<span>---</span>12]3</em></b></p>
|
|
if (start && start == end)
|
|
dom.replace(dom.create('span', {id : '__end'}, ec.cloneNode(true)), ec);
|
|
|
|
// Split all start containers down to the format root
|
|
if (start)
|
|
start = dom.split(start, sc);
|
|
else
|
|
start = sc;
|
|
|
|
// If there is a span wrapper use that one instead
|
|
if (n = dom.get('__end')) {
|
|
ec = n;
|
|
end = findFormatRoot(ec);
|
|
}
|
|
|
|
// Split all end containers down to the format root
|
|
if (end)
|
|
end = dom.split(end, ec);
|
|
else
|
|
end = ec;
|
|
|
|
// Collect nodes in between
|
|
processRange(dom, start, end, collect);
|
|
|
|
// Remove invisible character for IE workaround if we find it
|
|
if (sc.nodeValue == '\uFEFF')
|
|
sc.nodeValue = '';
|
|
|
|
// Process start/end container elements
|
|
walk(ec);
|
|
walk(sc);
|
|
}
|
|
|
|
// Remove all collected nodes
|
|
tinymce.each(nodes, function(n) {
|
|
dom.remove(n, 1);
|
|
});
|
|
|
|
// Remove leftover wrapper
|
|
dom.remove('__end', 1);
|
|
|
|
s.moveToBookmark(bm);
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
tinymce.GlobalCommands.add('mceBlockQuote', function() {
|
|
var ed = this, s = ed.selection, dom = ed.dom, sb, eb, n, bm, bq, r, bq2, i, nl;
|
|
|
|
function getBQ(e) {
|
|
return dom.getParent(e, function(n) {return n.nodeName === 'BLOCKQUOTE';});
|
|
};
|
|
|
|
// Get start/end block
|
|
sb = dom.getParent(s.getStart(), dom.isBlock);
|
|
eb = dom.getParent(s.getEnd(), dom.isBlock);
|
|
|
|
// Remove blockquote(s)
|
|
if (bq = getBQ(sb)) {
|
|
if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR'))
|
|
bm = s.getBookmark();
|
|
|
|
// Move all elements after the end block into new bq
|
|
if (getBQ(eb)) {
|
|
bq2 = bq.cloneNode(false);
|
|
|
|
while (n = eb.nextSibling)
|
|
bq2.appendChild(n.parentNode.removeChild(n));
|
|
}
|
|
|
|
// Add new bq after
|
|
if (bq2)
|
|
dom.insertAfter(bq2, bq);
|
|
|
|
// Move all selected blocks after the current bq
|
|
nl = s.getSelectedBlocks(sb, eb);
|
|
for (i = nl.length - 1; i >= 0; i--) {
|
|
dom.insertAfter(nl[i], bq);
|
|
}
|
|
|
|
// Empty bq, then remove it
|
|
if (/^\s*$/.test(bq.innerHTML))
|
|
dom.remove(bq, 1); // Keep children so boomark restoration works correctly
|
|
|
|
// Empty bq, then remote it
|
|
if (bq2 && /^\s*$/.test(bq2.innerHTML))
|
|
dom.remove(bq2, 1); // Keep children so boomark restoration works correctly
|
|
|
|
if (!bm) {
|
|
// Move caret inside empty block element
|
|
if (!tinymce.isIE) {
|
|
r = ed.getDoc().createRange();
|
|
r.setStart(sb, 0);
|
|
r.setEnd(sb, 0);
|
|
s.setRng(r);
|
|
} else {
|
|
s.select(sb);
|
|
s.collapse(0);
|
|
|
|
// IE misses the empty block some times element so we must move back the caret
|
|
if (dom.getParent(s.getStart(), dom.isBlock) != sb) {
|
|
r = s.getRng();
|
|
r.move('character', -1);
|
|
r.select();
|
|
}
|
|
}
|
|
} else
|
|
ed.selection.moveToBookmark(bm);
|
|
|
|
return;
|
|
}
|
|
|
|
// Since IE can start with a totally empty document we need to add the first bq and paragraph
|
|
if (tinymce.isIE && !sb && !eb) {
|
|
ed.getDoc().execCommand('Indent');
|
|
n = getBQ(s.getNode());
|
|
n.style.margin = n.dir = ''; // IE adds margin and dir to bq
|
|
return;
|
|
}
|
|
|
|
if (!sb || !eb)
|
|
return;
|
|
|
|
// If empty paragraph node then do not use bookmark
|
|
if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR'))
|
|
bm = s.getBookmark();
|
|
|
|
// Move selected block elements into a bq
|
|
tinymce.each(s.getSelectedBlocks(getBQ(s.getStart()), getBQ(s.getEnd())), function(e) {
|
|
// Found existing BQ add to this one
|
|
if (e.nodeName == 'BLOCKQUOTE' && !bq) {
|
|
bq = e;
|
|
return;
|
|
}
|
|
|
|
// No BQ found, create one
|
|
if (!bq) {
|
|
bq = dom.create('blockquote');
|
|
e.parentNode.insertBefore(bq, e);
|
|
}
|
|
|
|
// Add children from existing BQ
|
|
if (e.nodeName == 'BLOCKQUOTE' && bq) {
|
|
n = e.firstChild;
|
|
|
|
while (n) {
|
|
bq.appendChild(n.cloneNode(true));
|
|
n = n.nextSibling;
|
|
}
|
|
|
|
dom.remove(e);
|
|
return;
|
|
}
|
|
|
|
// Add non BQ element to BQ
|
|
bq.appendChild(dom.remove(e));
|
|
});
|
|
|
|
if (!bm) {
|
|
// Move caret inside empty block element
|
|
if (!tinymce.isIE) {
|
|
r = ed.getDoc().createRange();
|
|
r.setStart(sb, 0);
|
|
r.setEnd(sb, 0);
|
|
s.setRng(r);
|
|
} else {
|
|
s.select(sb);
|
|
s.collapse(1);
|
|
}
|
|
} else
|
|
s.moveToBookmark(bm);
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
tinymce.each(['Cut', 'Copy', 'Paste'], function(cmd) {
|
|
tinymce.GlobalCommands.add(cmd, function() {
|
|
var ed = this, doc = ed.getDoc();
|
|
|
|
try {
|
|
doc.execCommand(cmd, false, null);
|
|
|
|
// On WebKit the command will just be ignored if it's not enabled
|
|
if (!doc.queryCommandEnabled(cmd))
|
|
throw 'Error';
|
|
} catch (ex) {
|
|
if (tinymce.isGecko) {
|
|
ed.windowManager.confirm(ed.getLang('clipboard_msg'), function(s) {
|
|
if (s)
|
|
open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
|
|
});
|
|
} else
|
|
ed.windowManager.alert(ed.getLang('clipboard_no_support'));
|
|
}
|
|
});
|
|
});
|
|
})(tinymce);
|
|
(function(tinymce) {
|
|
tinymce.GlobalCommands.add('InsertHorizontalRule', function() {
|
|
if (tinymce.isOpera)
|
|
return this.getDoc().execCommand('InsertHorizontalRule', false, '');
|
|
|
|
this.selection.setContent('<hr />');
|
|
});
|
|
})(tinymce);
|
|
(function() {
|
|
var cmds = tinymce.GlobalCommands;
|
|
|
|
cmds.add(['mceEndUndoLevel', 'mceAddUndoLevel'], function() {
|
|
this.undoManager.add();
|
|
});
|
|
|
|
cmds.add('Undo', function() {
|
|
var ed = this;
|
|
|
|
if (ed.settings.custom_undo_redo) {
|
|
ed.undoManager.undo();
|
|
ed.nodeChanged();
|
|
return true;
|
|
}
|
|
|
|
return false; // Run browser command
|
|
});
|
|
|
|
cmds.add('Redo', function() {
|
|
var ed = this;
|
|
|
|
if (ed.settings.custom_undo_redo) {
|
|
ed.undoManager.redo();
|
|
ed.nodeChanged();
|
|
return true;
|
|
}
|
|
|
|
return false; // Run browser command
|
|
});
|
|
})();
|