📄 richtextcanvas.js
字号:
// apply the properties (keyName, etc.) to EH.lastEvent isc.EH.getKeyEventProperties(event); return isc.EH.handleKeyDown(event, {keyTarget:this}); }, _iFrameKeyUp : function (event) { // apply the properties (keyName, etc.) to EH.lastEvent isc.EH.getKeyEventProperties(event); return isc.EH.handleKeyUp(event, {keyTarget:this}); }, // If using designMode, we need a handler for the native scroll event on our IFRAME // to update the stored scroll position of the handle on scroll. // The standard handleCSSScroll method will handle scrolling (as it will check the // scroll position of this.getScrollHandle() - which points at the IFRAME). _iFrameScroll : function (event) { return this._handleCSSScroll(event); }, // Adjust overflow on keypress - updates recorded scroll width/height _$br:"<BR>", _$Enter:"Enter", // set of keys that are ignored by handleKeyPress because they can't modify the contents of // the editable area. This isn't exhaustive - the main reason to have these is to // eliminate gratuitous syntax hilighting while e.g. the user is using arrow keys to // navigate around the document. ignoreKeys : ["Arrow_Up", "Arrow_Down", "Arrow_Left", "Arrow_Right", "Ctrl", "Alt"], handleKeyPress : function (event, eventInfo) { var key = isc.EH.getKey(); if (this.ignoreKeys.contains(key)) return isc.EH.STOP_BUBBLING; // figure out the start line number of the current selection before the key stroke so // we can extract the modified line(s) later. if (this.countLines) this.rememberSelectionStartLine(); this._queueContentsChanged(); var returnVal = this.Super("handleKeyPress", arguments); // in IE, we set a timer onpaste to do syntax hiliting - this enalbes us to respond to // a paste command initiated via the context menu or "Edit" menu, but when a user hits // a keyboard shortcut to paste, we also get a key press. So if we're responding to a // keypress and there's a paste timer, we delete it so we don't process the paste twice. if (isc.Browser.isIE && this._pasteTimer) { isc.Timer.clearTimeout(this._pasteTimer); delete this._pasteTimer; } if (returnVal != false && isc.Browser.isIE && key == this._$Enter) { this._rememberSelection(); this._savedSelection.pasteHTML(this._$br); this._savedSelection.collapse(true); this._savedSelection.select(); returnVal = false; } return returnVal; }, _queueContentsChanged : function () { if (!this._dirtyContent) { this._dirtyContent = true; if (!this._changedHandlerName) this._changedHandlerName = "_contentsChanged"; isc.Page.setEvent(isc.EH.IDLE, this, isc.Page.FIRE_ONCE, this._changedHandlerName); } }, //_contentsChanged - fired when the contents is edited. // Not fired in response to explicit 'setContents' call. _contentsChanged : function () { delete this._dirtyContent; var oldVal = this.contents, newVal = this.getContents(); if (oldVal == newVal) return; // if we're counting lines, then call doLinesChanged(). We're also checking for // selectionIsCollapsed() here because Ctrl-A, which causes all contents to be selected // should not fire doLinesChanged() - and that's the only known way to get a multichar // selection after a keystroke/paste event if (this.countLines && this.selectionIsCollapsed()) this.doLinesChanged(oldVal, newVal); // AdjustOverflow - our scroll-size is likely to have changed this.adjustOverflow("edited"); // Fire this.changed, if present if (this.changed != null) this.changed(oldVal, newVal); this.contents = newVal; }, // ------------------------------------------------------------------------------------ // lineChanged / Synax Hiliting support // ------------------------------------------------------------------------------------ // // We want to detect any change made to the editable area so we can re-format the view. // Change can be effected in several ways: // - keypress // - paste action using the browser "Edit" menu // - programmatic update (currently only via setContents()) // // At the time of the change, the user may have an insertion cursor or a block of selected // text. // // We want to format the newly added data, and potentially some data around the new data. // To that end we want to: // - mark the location of the current insertion point, so we can restore it after making // changes // - determine the start and end index of the changed text // - expand the above indexes to fully envelop the start and end line of the changed // text. // - we want this because recolorization will happen on a line-by-line basis (for // performance reasons) except for some cases where we'll want to recolorize the // whole document // // In IE and FF we can insert arbitrary HTML at the insertion cursor. This allows us to // get the current location of the cursor. // // General issues //--------------- // - In FF, we can't detect what was pasted unless we compare the original contents // and the new contents - which is expensive. In fact, if the user uses the Edit // menu paste command, then we can't tell the the editable area even changed. There's a // DOM event called "onsubtreemodified" that's part of the w3c spec that should at least // tell us that the contents changed - but it doesn't work at all in current versions of FF // (and this has been corroborated by postings on the web). // - In FF, the contents of the editable area change asynchronously with the key event. In // other words you have to set a timeout to get the after-changed state of the editable area. // - Detecting current selection/insertion point. In IE and FF we can detect the current // selection/insertion point by wrapping the selection contents in a DOM node (e.g. a span) // and then scanning the contents for it. // - In FF we lose the insertion cursor if there's no adjoining text. Also, the cursor // marker that we use to extract the cursor position blocks the user from using the right // arrow on the keyboard to move to the next character beyond it. // // Approaches: // ----------- // 1. Wait for the user to stop typing for a bit and then reformat the entire editable // area. Performance is gated by the formatting algorithm, but this is very easy to // implement and may be acceptable for some use cases. // - benefits // - easy to implement // - problems // - doesn't look as nice, because formatting is not realtime // // 2. Determine the edited line by scanning backwards and forwards from the current // insertion point looking for <BR>s. // - benefits // - easier than maintaining line information in the editable area // - problems // - scanning backwards for <BR>s potentially not cheap // - need to both scan backwards for <BR> (so we can select out the HTML to pass to the // formatter) and walk the DOM to the last <BR> so we can efficiently insert the results. // // 3. Maintain line information in the editable area by inserting line spans into the // contents provided to setContents(). Insert a span with a unique ID into the document at // the current insertion point after a change and walk the // DOM up from that node to find the current line for fast extraction. // - benefits: // - very fast line extraction // - given a selection, can quickly determine what lines it spans. // - after formatting the line, we can replace it effieciently via innerHTML assignment // or equivalent. // - As long as line-based formatting is sufficient for most keystrokes, this approach // gives us an O(1) implementation. // - can get the contents of any line very quickly. Given a line, can get its immediate // surrounding lines quickly. Can get the line number quickly. // - problems // - These spans must be maintained as the user hits Backspace, Enter and pastes // arbitrary content. // - this means fragmenting multiline pastes and those created by user hitting the // Enter key into separate lines and combining lines created by partial line pastes and // Backspace at beginning of line. // - In IE, copying a whole line out of the editable area also picks up its line span // delimiters, which means we can end up with a line inside a line situation. // - I think this can be fixed by defining a custom onbeforepaste handler on the // line spans and filtering the line spans out of the data retrieved from the // clipboard. // - In Moz, the <BR> inside the line span isn't picked up by the copy operation and // is replaced by the paste operation if the paste is at the end of the line. // Further, unlikes IE, the line span isn't placed inside the line that gets pasted // into - instead the pasted-into line is fragmented into two line spans by the // browser and the pasted line is added a peer in between those. // // // line behaviors // -------------- // - type character at beginning of line // - IE, FF: currentLine contains original chars + new char + selection marker + <br> // - type character in the middle of a line // - IE, FF: as above // - type character at end of a line // - IE, FF: as above // - no special processing required for the above // // - hit enter at beginning of line // - IE, FF: current line contains <BR><cursor>origLineText<BR> // - hit enter in the middle of a line // - IE, FF: current line contains origLineTextBeforeEnter<BR><cursor>origLineTextAfterEnter<BR> // - hit enter at the end of a line // - IE, FF: current line contains origLineText<BR><cursor><BR> // - split current line by <BR> count using regexes to extract new lines // // - hit backspace at beginning of line // - IE: <cursor> jumps to previous line, deleting the <BR> there. // - FF: previous line missing <BR>, currentLine has <cursor> at start // - hit delete at end of line // - IE: current line now missing <BR> (basically as IE above) // - FF: as IE // - hit backspace in middle of line // - IE, FF: as typing char in middle of line, except old char deleted // - hit backspace at end of line // - IE, FF: as above // - if current line is missing <BR>, combine with next, otherwise combine previous with current // // - paste external chars (not from a line span) with no BR in line: // - IE: as typing char, but selection goes to front of pasted text // - FF: as typing char // - paste external chars (not from a line span) with BR in line: // - IE, FF: as above, plus a <BR> where there's a linebreak in pasted content // - handled by above cases // // - paste chars from a line span with no BR in line: // - IE: if the copied selection touched the start of the line span, then behaves as FF, // except that the pasted line span appears as a child of the pasted-to line span, not a peer. // Otherwise behaves as paste of external chars // - filter out line spans from pasted content with onbeforepaste, onpaste on line spans, // then handled by above cases as external paste // // - FF: pasted-to line is fragmented into two line spans by the insertion point. pasted // text appears as its own line in between the two. If pasted at end of a line, that // line's BR is moved out of that line's span as the nextSibling the newly created // line span. // - whenever pasting into a line, that line's <BR> is destroyed and either // moves outside the line span (if paste was at end of line) or into a new line // fragment (if there was text to the right of the insertion cursor before paste) // - a paste can be multiline, introducing potentially multiple whole lines, so can't // just look back for a missing <BR> until we hit a normal line (since some intervening // lines may actually be normal, because pasted wholesale from this edit area) // - SOLUTION: // - before paste: // - query total number of lines // - after paste: // - if <BR> is nextSibling outside currentLine (selection always at end of paste), // pull it into currentLine. // - now query total number of lines (after paste). This is the number of lines we // have to walk back looking for missing <BR>s and combining. // // Actual approach used: //---------------------- // #3, except don't combine lines in realtime. Instead, provide a special change() // notification that gives the user the pasted contents and the pasted contents out to line // breaks, and the line number. The user can then format them and call a method to replace // them and insert them at a given location. // // apply a SyntaxHiliter to the contents setSyntaxHiliter : function (syntaxHiliter) { if (syntaxHiliter == null) { this.removeSyntaxHiliter; return; } this.syntaxHiliter = syntaxHiliter; this.countLines = true; // apply syntax hiliting to the contents var contents = this.getContents() || isc.emptyString; this.setContents(contents); }, removeSyntaxHiliter : function () { // get the contents (do this before we delete this.syntaxHiliter, otherwise the // contents will come with markup. var contents = this.getContents() || isc.emptyString; delete this.syntaxHiliter; delete this.countLines; // apply the contents, now without the markup this.setContents(contents); }, doLinesChanged : function (oldVal, newVal) {// this.logWarn("doLinesChanged - newVal: " + newVal); var startLineNum = this.getLastSelectionStartLine(); // initial setContents() only if (startLineNum == null) return; var startLine = this.getLine(startLineNum);// this.logWarn("startLineNum: " + startLineNum);// if (!startLine) this.logWarn("startLine is null");// else this.logWarn("startLine: " + startLine.innerHTML); var html = isc.emptyString; var selectionId = this.markCurrentSelection(); if (isc.Browser.isIE) { // startLine contains everything we need - in the event that lines from the editor // got pasted back in, those lines appear as children of the current line and the // line markers will just get stripped out by unescapeHTML() if (!startLine) { this.getLineContainer().innerHTML = isc.emptyString; var line = this.createLine(); this.getLineContainer().appendChild(line); var range = document.selection.createRange(); range.moveToElementText(line); range.collapse(); range.select(); selectionId = this.markCurrentSelection();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -