WIP: Note editing, markdown to html
[oweals/karmaworld.git] / karmaworld / apps / wysihtml5 / static / wysihtml5 / wysihtml-0.4.17 / src / editor.js
1 /**
2  * WYSIHTML5 Editor
3  *
4  * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface
5  * @param {Object} [config] See defaultConfig object below for explanation of each individual config option
6  *
7  * @events
8  *    load
9  *    beforeload (for internal use only)
10  *    focus
11  *    focus:composer
12  *    focus:textarea
13  *    blur
14  *    blur:composer
15  *    blur:textarea
16  *    change
17  *    change:composer
18  *    change:textarea
19  *    paste
20  *    paste:composer
21  *    paste:textarea
22  *    newword:composer
23  *    destroy:composer
24  *    undo:composer
25  *    redo:composer
26  *    beforecommand:composer
27  *    aftercommand:composer
28  *    enable:composer
29  *    disable:composer
30  *    change_view
31  */
32 (function(wysihtml5) {
33   var undef;
34
35   var defaultConfig = {
36     // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body
37     name:                 undef,
38     // Whether the editor should look like the textarea (by adopting styles)
39     style:                true,
40     // Id of the toolbar element, pass falsey value if you don't want any toolbar logic
41     toolbar:              undef,
42     // Whether toolbar is displayed after init by script automatically.
43     // Can be set to false if toolobar is set to display only on editable area focus
44     showToolbarAfterInit: true,
45     // Whether urls, entered by the user should automatically become clickable-links
46     autoLink:             true,
47     // Includes table editing events and cell selection tracking
48     handleTables:         true,
49     // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
50     handleTabKey:         true,
51     // Object which includes parser rules to apply when html gets cleaned
52     // See parser_rules/*.js for examples
53     parserRules:          { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
54     // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead
55     pasteParserRulesets: null,
56     // Parser method to use when the user inserts content
57     parser:               wysihtml5.dom.parse,
58     // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
59     composerClassName:    "wysihtml5-editor",
60     // Class name to add to the body when the wysihtml5 editor is supported
61     bodyClassName:        "wysihtml5-supported",
62     // By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
63     useLineBreaks:        true,
64     // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
65     stylesheets:          [],
66     // Placeholder text to use, defaults to the placeholder attribute on the textarea element
67     placeholderText:      undef,
68     // Whether the rich text editor should be rendered on touch devices (wysihtml5 >= 0.3.0 comes with basic support for iOS 5)
69     supportTouchDevices:  true,
70     // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content
71     cleanUp:              true,
72     // Whether to use div instead of secure iframe
73     contentEditableMode: false,
74     // Classname of container that editor should not touch and pass through
75     // Pass false to disable
76     uneditableContainerClassname: "wysihtml5-uneditable-container",
77     // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste)
78     // Also copied source is based directly on selection - 
79     // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection).
80     // If falsy value is passed source override is also disabled
81     copyedFromMarking: '<meta name="copied-from" content="wysihtml5">'
82   };
83
84   wysihtml5.Editor = wysihtml5.lang.Dispatcher.extend(
85     /** @scope wysihtml5.Editor.prototype */ {
86     constructor: function(editableElement, config) {
87       this.editableElement  = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement;
88       this.config           = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get();
89       this._isCompatible    = wysihtml5.browser.supported();
90
91       if (this.editableElement.nodeName.toLowerCase() != "textarea") {
92           this.config.contentEditableMode = true;
93           this.config.noTextarea = true;
94       }
95       if (!this.config.noTextarea) {
96           this.textarea         = new wysihtml5.views.Textarea(this, this.editableElement, this.config);
97           this.currentView      = this.textarea;
98       }
99
100       // Sort out unsupported/unwanted browsers here
101       if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml5.browser.isTouchDevice())) {
102         var that = this;
103         setTimeout(function() { that.fire("beforeload").fire("load"); }, 0);
104         return;
105       }
106
107       // Add class name to body, to indicate that the editor is supported
108       wysihtml5.dom.addClass(document.body, this.config.bodyClassName);
109
110       this.composer = new wysihtml5.views.Composer(this, this.editableElement, this.config);
111       this.currentView = this.composer;
112
113       if (typeof(this.config.parser) === "function") {
114         this._initParser();
115       }
116
117       this.on("beforeload", this.handleBeforeLoad);
118     },
119
120     handleBeforeLoad: function() {
121         if (!this.config.noTextarea) {
122             this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer);
123         }
124         if (this.config.toolbar) {
125           this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar, this.config.showToolbarAfterInit);
126         }
127     },
128
129     isCompatible: function() {
130       return this._isCompatible;
131     },
132
133     clear: function() {
134       this.currentView.clear();
135       return this;
136     },
137
138     getValue: function(parse, clearInternals) {
139       return this.currentView.getValue(parse, clearInternals);
140     },
141
142     setValue: function(html, parse) {
143       this.fire("unset_placeholder");
144
145       if (!html) {
146         return this.clear();
147       }
148
149       this.currentView.setValue(html, parse);
150       return this;
151     },
152
153     cleanUp: function() {
154         this.currentView.cleanUp();
155     },
156
157     focus: function(setToEnd) {
158       this.currentView.focus(setToEnd);
159       return this;
160     },
161
162     /**
163      * Deactivate editor (make it readonly)
164      */
165     disable: function() {
166       this.currentView.disable();
167       return this;
168     },
169
170     /**
171      * Activate editor
172      */
173     enable: function() {
174       this.currentView.enable();
175       return this;
176     },
177
178     isEmpty: function() {
179       return this.currentView.isEmpty();
180     },
181
182     hasPlaceholderSet: function() {
183       return this.currentView.hasPlaceholderSet();
184     },
185
186     parse: function(htmlOrElement, clearInternals) {
187       var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null);
188       var returnValue = this.config.parser(htmlOrElement, {
189         "rules": this.config.parserRules,
190         "cleanUp": this.config.cleanUp,
191         "context": parseContext,
192         "uneditableClass": this.config.uneditableContainerClassname,
193         "clearInternals" : clearInternals
194       });
195       if (typeof(htmlOrElement) === "object") {
196         wysihtml5.quirks.redraw(htmlOrElement);
197       }
198       return returnValue;
199     },
200
201     /**
202      * Prepare html parser logic
203      *  - Observes for paste and drop
204      */
205     _initParser: function() {
206       var that = this,
207           oldHtml,
208           cleanHtml;
209
210       if (wysihtml5.browser.supportsModenPaste()) {
211         this.on("paste:composer", function(event) {
212           event.preventDefault();
213           oldHtml = wysihtml5.dom.getPastedHtml(event);
214           if (oldHtml) {
215             that._cleanAndPaste(oldHtml);
216           }
217         });
218
219       } else {
220         this.on("beforepaste:composer", function(event) {
221           event.preventDefault();
222           wysihtml5.dom.getPastedHtmlWithDiv(that.composer, function(pastedHTML) {
223             if (pastedHTML) {
224               that._cleanAndPaste(pastedHTML);
225             }
226           });
227         });
228
229       }
230     },
231
232     _cleanAndPaste: function (oldHtml) {
233       var cleanHtml = wysihtml5.quirks.cleanPastedHTML(oldHtml, {
234         "referenceNode": this.composer.element,
235         "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}],
236         "uneditableClass": this.config.uneditableContainerClassname
237       });
238       this.composer.selection.deleteContents();
239       this.composer.selection.insertHTML(cleanHtml);
240     }
241   });
242 })(wysihtml5);