From 055477e45d841fa4752601d85b5f796c148c61a5 Mon Sep 17 00:00:00 2001 From: Jesse Weaver Date: Tue, 11 Mar 2014 11:28:40 +0100 Subject: [PATCH] Bug 11559 - Rancor: professional cataloging interface Test plan: 1. Apply patches for bug 10486 and this bug. 2. Reinstall patches for pazpar2. 3. Switch back and forth between basic and advanced editor, and verify that preference is saved (by workstation, not user. 4. Enter in a record (with all required fields). 5. Save to ISO2709, MARCXML and catalog, verify that all three match. 6. Import a MARCXML and ISO2709 record, and verify that the record is correct after import. 7. Run keyword and advanced searches with a variety of Z39.50 servers to check that searching is correct, and try to both import records as new records and substitute them for the contents of existing records. 8. Create, edit and delete macros, verifying that they are autosaved. 9. Try running macros (example follows). 10. Try enabling and disabling fixed-field widgets and customizing fonts, and test the editor afterwards. Example macro: goto field 003 copy field data insert new field 040 data= goto subfield end insert new subfield a data= goto subfield a paste --- cataloguing/addbiblio.pl | 6 + cataloguing/editor.pl | 53 + .../lib/codemirror/codemirror-compressed.js | 11 + .../intranet-tmpl/lib/codemirror/codemirror.js | 5715 -------------------- .../intranet-tmpl/lib/codemirror/lib/runmode.js | 56 - .../intranet-tmpl/lib/jquery/plugins/humanmsg.js | 26 +- .../lib/koha/cateditor/koha-backend.js | 201 + .../intranet-tmpl/lib/koha/cateditor/macros.js | 196 + .../lib/koha/cateditor/marc-editor.js | 358 ++ .../intranet-tmpl/lib/koha/cateditor/marc-mode.js | 129 + .../lib/koha/cateditor/marc-record.js | 351 ++ .../lib/koha/cateditor/preferences.js | 28 + .../intranet-tmpl/lib/koha/cateditor/resources.js | 19 + .../intranet-tmpl/lib/koha/cateditor/search.js | 96 + .../intranet-tmpl/lib/koha/cateditor/text-marc.js | 78 + .../intranet-tmpl/lib/koha/cateditor/widget.js | 287 + koha-tmpl/intranet-tmpl/prog/en/css/cateditor.css | 402 ++ .../prog/en/includes/cateditor-ui.inc | 835 +++ .../prog/en/includes/cateditor-widgets-marc21.inc | 158 + .../prog/en/modules/cataloguing/addbiblio.tt | 22 +- .../prog/en/modules/cataloguing/addbooks.tt | 6 + .../prog/en/modules/cataloguing/editor.tt | 213 + svc/cataloguing/framework | 73 + 23 files changed, 3535 insertions(+), 5784 deletions(-) create mode 100755 cataloguing/editor.pl create mode 100644 koha-tmpl/intranet-tmpl/lib/codemirror/codemirror-compressed.js delete mode 100644 koha-tmpl/intranet-tmpl/lib/codemirror/codemirror.js delete mode 100644 koha-tmpl/intranet-tmpl/lib/codemirror/lib/runmode.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/koha-backend.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/macros.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-editor.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-mode.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-record.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/resources.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/search.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/text-marc.js create mode 100644 koha-tmpl/intranet-tmpl/lib/koha/cateditor/widget.js create mode 100644 koha-tmpl/intranet-tmpl/prog/en/css/cateditor.css create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/editor.tt create mode 100755 svc/cataloguing/framework diff --git a/cataloguing/addbiblio.pl b/cataloguing/addbiblio.pl index 118bfc2..01291ee 100755 --- a/cataloguing/addbiblio.pl +++ b/cataloguing/addbiblio.pl @@ -767,8 +767,14 @@ if ($frameworkcode eq 'FA'){ 'stickyduedate' => $fa_stickyduedate, 'duedatespec' => $fa_duedatespec, ); +} elsif ( $input->cookie( 'catalogue_editor_' . $loggedinuser ) eq 'advanced' && !$breedingid ) { + # Only use the advanced editor for non-fast-cataloging. + # breedingid is not handled because those would only come off a Z39.50 + # search initiated by the basic editor. + print $input->redirect( '/cgi-bin/koha/cataloguing/editor.pl' . ( $biblionumber ? ( '#catalog:' . $biblionumber ) : '' ) ); } + # Getting the list of all frameworks # get framework list my $frameworks = getframeworks; diff --git a/cataloguing/editor.pl b/cataloguing/editor.pl new file mode 100755 index 0000000..fe9d897 --- /dev/null +++ b/cataloguing/editor.pl @@ -0,0 +1,53 @@ +#!/usr/bin/perl +# +# Copyright 2013 ByWater +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +use Modern::Perl '2009'; + +use CGI; +use MARC::Record; + +use C4::Auth; +use C4::Biblio; +use C4::Context; +use C4::Output; + +my $input = CGI->new; + +my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { + template_name => 'cataloguing/editor.tt', + query => $input, + type => 'intranet', + authnotrequired => 0, + flagsrequired => { editcatalogue => 'edit_catalogue' }, + } +); + +# Needed information for cataloging plugins +$template->{VARS}->{DefaultLanguageField008} = pack( 'A3', C4::Context->preference('DefaultLanguageField008') || 'eng' ); + +# Z39.50 servers +my $dbh = C4::Context->dbh; +$template->{VARS}->{z3950_targets} = $dbh->selectall_arrayref( q{ + SELECT * FROM z3950servers + ORDER BY name +}, { Slice => {} } ); + +output_html_with_http_headers $input, $cookie, $template->output; diff --git a/koha-tmpl/intranet-tmpl/lib/codemirror/codemirror-compressed.js b/koha-tmpl/intranet-tmpl/lib/codemirror/codemirror-compressed.js new file mode 100644 index 0000000..6ff3838 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/codemirror/codemirror-compressed.js @@ -0,0 +1,11 @@ +// CodeMirror 3.14 +// +// Licensed under the MIT license. +// Copyright (C) 2014 by Marijn Haverbeke and others. +// +// Packaged along with the runmode addon, using the compression helper at codemirror.net. +// +window.CodeMirror=function(){"use strict";function w(a,c){if(!(this instanceof w))return new w(a,c);this.options=c=c||{};for(var d in Wc)!c.hasOwnProperty(d)&&Wc.hasOwnProperty(d)&&(c[d]=Wc[d]);I(c);var e="string"==typeof c.value?0:c.value.first,f=this.display=x(a,e);f.wrapper.CodeMirror=this,F(this),c.autofocus&&!o&&Kb(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,focused:!1,suppressEdits:!1,pasteIncoming:!1,draggingText:!1,highlight:new Qe},D(this),c.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap");var g=c.value;"string"==typeof g&&(g=new ae(c.value,c.mode)),Cb(this,ee)(this,g),b&&setTimeout($e(Jb,this,!0),20),Mb(this);var h;try{h=document.activeElement==f.input}catch(i){}h||c.autofocus&&!o?setTimeout($e(hc,this),20):ic(this),Cb(this,function(){for(var a in Vc)Vc.propertyIsEnumerable(a)&&Vc[a](this,c[a],Yc);for(var b=0;bb.maxLineLength&&(b.maxLineLength=d,b.maxLine=a)})}function I(a){for(var b=!1,c=0;cb.scroller.clientWidth+1,g=e>b.scroller.clientHeight+1;g?(b.scrollbarV.style.display="block",b.scrollbarV.style.bottom=f?mf(b.measure)+"px":"0",b.scrollbarV.firstChild.style.height=e-b.scroller.clientHeight+b.scrollbarV.clientHeight+"px"):b.scrollbarV.style.display="",f?(b.scrollbarH.style.display="block",b.scrollbarH.style.right=g?mf(b.measure)+"px":"0",b.scrollbarH.firstChild.style.width=b.scroller.scrollWidth-b.scroller.clientWidth+b.scrollbarH.clientWidth+"px"):b.scrollbarH.style.display="",f&&g?(b.scrollbarFiller.style.display="block",b.scrollbarFiller.style.height=b.scrollbarFiller.style.width=mf(b.measure)+"px"):b.scrollbarFiller.style.display="",f&&a.options.coverGutterNextToScrollbar&&a.options.fixedGutter?(b.gutterFiller.style.display="block",b.gutterFiller.style.height=mf(b.measure)+"px",b.gutterFiller.style.width=b.gutters.offsetWidth+"px"):b.gutterFiller.style.display="",k&&0===mf(b.measure)&&(b.scrollbarV.style.minWidth=b.scrollbarH.style.minHeight=l?"18px":"12px")}function K(a,b,c){var d=a.scroller.scrollTop,e=a.wrapper.clientHeight;"number"==typeof c?d=c:c&&(d=c.top,e=c.bottom-c.top),d=Math.floor(d-db(a));var f=Math.ceil(d+e);return{from:ke(b,d),to:ke(b,f)}}function L(a){var b=a.display;if(b.alignWidgets||b.gutters.firstChild&&a.options.fixedGutter){for(var c=O(b)-b.scroller.scrollLeft+a.doc.scrollLeft,d=b.gutters.offsetWidth,e=c+"px",f=b.lineDiv.firstChild;f;f=f.nextSibling)if(f.alignable)for(var g=0,h=f.alignable;g=a.display.showingFrom&&g.to<=a.display.showingTo));)b=[];return f&&(Ke(a,"update",a),(a.display.showingFrom!=d||a.display.showingTo!=e)&&Ke(a,"viewportChange",a,a.display.showingFrom,a.display.showingTo)),f}function Q(a,b,d){var e=a.display,f=a.doc;if(!e.wrapper.clientWidth)return e.showingFrom=e.showingTo=f.first,e.viewOffset=0,void 0;if(!(0==b.length&&d.from>e.showingFrom&&d.tol&&e.showingTo-l<20&&(l=Math.min(j,e.showingTo)),v)for(k=je(zd(f,fe(f,k)));j>l&&Ad(f,fe(f,l));)++l;var m=[{from:Math.max(e.showingFrom,f.first),to:Math.min(e.showingTo,j)}];if(m=m[0].from>=m[0].to?[]:S(m,b),v)for(var i=0;in.from)){m.splice(i--,1);break}n.to=p}for(var q=0,i=0;il&&(n.to=l),n.from>=n.to?m.splice(i--,1):q+=n.to-n.from}if(q==l-k&&k==e.showingFrom&&l==e.showingTo)return R(a),void 0;m.sort(function(a,b){return a.from-b.from});try{var r=document.activeElement}catch(s){}.7*(l-k)>q&&(e.lineDiv.style.display="none"),U(a,k,l,m,h),e.lineDiv.style.display="",r&&document.activeElement!=r&&r.offsetHeight&&r.focus();var t=k!=e.showingFrom||l!=e.showingTo||e.lastSizeC!=e.wrapper.clientHeight;t&&(e.lastSizeC=e.wrapper.clientHeight,_(a,400)),e.showingFrom=k,e.showingTo=l;for(var x,u=e.lineDiv.offsetTop,w=e.lineDiv.firstChild;w;w=w.nextSibling)if(w.lineObj){if(c){var y=w.offsetTop+w.offsetHeight;x=y-u,u=y}else{var z=hf(w);x=z.bottom-z.top}var A=w.lineObj.height-x;if(2>x&&(x=xb(e)),A>.001||-.001>A){ie(w.lineObj,x);var B=w.lineObj.widgets;if(B)for(var i=0;ic;++c){for(var e=b[c],f=[],g=e.diff||0,h=0,i=a.length;i>h;++h){var j=a[h];e.to<=j.from&&e.diff?f.push({from:j.from+g,to:j.to+g}):e.to<=j.from||e.from>=j.to?f.push(j):(e.from>j.from&&f.push({from:j.from,to:e.from}),e.ton){for(;k.lineObj!=b;)k=l(k);i&&n>=f&&k.lineNumber&&gf(k.lineNumber,N(a.options,n)),k=k.nextSibling}else{if(b.widgets)for(var s,q=0,r=k;r&&20>q;++q,r=r.nextSibling)if(r.lineObj==b&&/div/i.test(r.nodeName)){s=r;break}var t=V(a,b,n,g,s);if(t!=s)j.insertBefore(t,k);else{for(;k!=s;)k=l(k);k=k.nextSibling}t.lineObj=b}++n});k;)k=l(k)}function V(a,b,d,e,f){var j,g=Rd(a,b),h=b.gutterMarkers,i=a.display;if(!(a.options.lineNumbers||h||b.bgClass||b.wrapClass||b.widgets))return g;if(f){f.alignable=null;for(var o,k=!0,l=0,m=null,n=f.firstChild;n;n=o)if(o=n.nextSibling,/\bCodeMirror-linewidget\b/.test(n.className)){for(var p=0,q=!0;pb&&(b=0),e.appendChild(df("div",null,"CodeMirror-selected","position: absolute; left: "+a+"px; top: "+b+"px; width: "+(null==c?f-a:c)+"px; height: "+(d-b)+"px"))}function i(b,d,e){function m(c,d){return rb(a,wc(b,c),"div",i,d)}var k,l,i=fe(c,b),j=i.text.length;return tf(me(i),d||0,null==e?j:e,function(a,b,c){var n,o,p,i=m(a,"left");if(a==b)n=i,o=p=i.left;else{if(n=m(b-1,"right"),"rtl"==c){var q=i;i=n,n=q}o=i.left,p=n.right}null==d&&0==a&&(o=g),n.top-i.top>3&&(h(o,i.top,null,i.bottom),o=g,i.bottoml.bottom||n.bottom==l.bottom&&n.right>l.right)&&(l=n),g+1>o&&(o=g),h(o,n.top,p-o,n.bottom)}),{start:k,end:l}}var b=a.display,c=a.doc,d=a.doc.sel,e=document.createDocumentFragment(),f=b.lineSpace.offsetWidth,g=fb(a.display);if(d.from.line==d.to.line)i(d.from.line,d.from.ch,d.to.ch);else{var j=fe(c,d.from.line),k=fe(c,d.to.line),l=zd(c,j)==zd(c,k),m=i(d.from.line,d.from.ch,l?j.text.length:null).end,n=i(d.to.line,l?0:null,d.to.ch).start;l&&(m.top=a.display.showingTo)){var f,c=+new Date+a.options.workTime,d=bd(b.mode,cb(a,b.frontier)),e=[];b.iter(b.frontier,Math.min(b.first+b.size,a.display.showingTo+500),function(g){if(b.frontier>=a.display.showingFrom){var h=g.styles;g.styles=Md(a,g,d);for(var i=!h||h.length!=g.styles.length,j=0;!i&&jc?(_(a,a.options.workDelay),!0):void 0}),e.length&&Cb(a,function(){for(var a=0;ah;--g){if(g<=f.first)return f.first;var i=fe(f,g-1);if(i.stateAfter&&(!c||g<=f.frontier))return g;var j=Re(i.text,null,a.options.tabSize);(null==e||d>j)&&(e=g-1,d=j)}return e}function cb(a,b,c){var d=a.doc,e=a.display;if(!d.mode.startState)return!0;var f=bb(a,b,c),g=f>d.first&&fe(d,f-1).stateAfter;return g=g?bd(d.mode,g):cd(d.mode),d.iter(f,b,function(c){Od(a,c,g);var h=f==b-1||0==f%5||f>=e.showingFrom&&ff&&0==g&&(f=1)}var i=(c>g||"right"==e)&&null!=h.topRight;return{left:c>g?h.right:h.left,right:g>c?h.left:h.right,top:i?h.topRight:h.top,bottom:i?h.bottomRight:h.bottom}}function hb(a,b){for(var c=a.display.measureLineCache,d=0;ds&&(b=s),0>a&&(a=0);for(var c=0;cb||a>e)&&(a>=d&&e>=b||d>=a&&b>=e||Math.min(b,e)-Math.max(a,d)>=b-a>>1))return q[c]=Math.min(a,d),q[c+1]=Math.max(b,e),c}return q.push(a,b),c}var f=a.display,g=Ze(e.text.length),h=Rd(a,e,g);if(b&&!c&&!a.options.lineWrapping&&h.childNodes.length>100){for(var i=document.createDocumentFragment(),j=10,k=h.childNodes.length,l=0,m=Math.ceil(k/j);m>l;++l){for(var n=df("div",null,null,"display: inline-block"),o=0;j>o&&k;++o)n.appendChild(h.firstChild),--k;i.appendChild(n)}h.appendChild(i)}ff(f.measure,h);var p=hf(f.lineDiv),q=[],r=Ze(e.text.length),s=h.offsetHeight;d&&f.measure.first!=h&&ff(f.measure,h);for(var u,l=0;l1){var A=t(y.top-p.top,y.bottom-p.top),B=t(z.top-p.top,z.bottom-p.top);r[l]={left:y.left-p.left,right:z.right-p.left,top:A,topRight:B};continue}}v=hf(w);var C=t(v.top-p.top,v.bottom-p.top),D=v.right;u.measureRight&&(D=hf(u.measureRight).left),r[l]={left:v.left-p.left,right:D-p.left,top:C}}for(var u,l=0;lc.from?f(a-1):f(a,d)}d=d||fe(a.doc,b.line),e||(e=jb(a,d));var h=me(d),i=b.ch;if(!h)return f(i);var j=Cf(h,i),k=g(i,j);return null!=Bf&&(k.other=g(i,Bf)),k}function tb(a,b,c,d){var e=new wc(a,b);return e.xRel=d,c&&(e.outside=!0),e}function ub(a,b,c){var d=a.doc;if(c+=a.display.viewOffset,0>c)return tb(d.first,0,!0,-1);var e=ke(d,c),f=d.first+d.size-1;if(e>f)return tb(d.first+d.size-1,fe(d,f).text.length,!0,1);for(0>b&&(b=0);;){var g=fe(d,e),h=vb(a,g,e,b,c),i=yd(g),j=i&&i.find();if(!i||!(h.ch>j.from.ch||h.ch==j.from.ch&&h.xRel>0))return h;e=j.to.line}}function vb(a,b,c,d,e){function j(d){var e=sb(a,wc(c,d),"line",b,i);return g=!0,f>e.bottom?e.left-h:fq)return tb(c,n,r,1);for(;;){if(k?n==m||n==Ef(b,m,1):1>=n-m){for(var s=o>d||q-d>=d-o?m:n,t=d-(s==m?o:q);cf.test(b.text.charAt(s));)++s;var u=tb(c,s,s==m?p:r,0>t?-1:t?1:0);return u}var v=Math.ceil(l/2),w=m+v;if(k){w=m;for(var x=0;v>x;++x)w=Ef(b,w,1)}var y=j(w);y>d?(n=w,q=y,(r=g)&&(q+=1e3),l=v):(m=w,o=y,p=g,l-=v)}}function xb(a){if(null!=a.cachedTextHeight)return a.cachedTextHeight;if(null==wb){wb=df("pre");for(var b=0;49>b;++b)wb.appendChild(document.createTextNode("x")),wb.appendChild(df("br"));wb.appendChild(document.createTextNode("x"))}ff(a.measure,wb);var c=wb.offsetHeight/50;return c>3&&(a.cachedTextHeight=c),ef(a.measure),c||1}function yb(a){if(null!=a.cachedCharWidth)return a.cachedCharWidth;var b=df("span","x"),c=df("pre",[b]);ff(a.measure,c);var d=b.offsetWidth;return d>2&&(a.cachedCharWidth=d),d||10}function Ab(a){a.curOp={changes:[],updateInput:null,userSelChange:null,textChanged:null,selectionChanged:!1,cursorActivity:!1,updateMaxLine:!1,updateScrollPos:!1,id:++zb},Je++||(Ie=[])}function Bb(a){var b=a.curOp,c=a.doc,d=a.display;if(a.curOp=null,b.updateMaxLine&&H(a),d.maxLineChanged&&!a.options.lineWrapping&&d.maxLine){var e=lb(a,d.maxLine);d.sizer.style.minWidth=Math.max(0,e+3+Oe)+"px",d.maxLineChanged=!1;var f=Math.max(0,d.sizer.offsetLeft+d.sizer.offsetWidth-d.scroller.clientWidth);fj&&e.charCodeAt(j)==h.charCodeAt(j);)++j;var l=g.from,m=g.to;j1e3||h.indexOf("\n")>-1?c.value=a.display.prevInput="":a.display.prevInput=h,i&&Bb(a),a.state.pasteIncoming=!1,!0}function Jb(a,c){var e,f,g=a.doc;if(xc(g.sel.from,g.sel.to))c&&(a.display.prevInput=a.display.input.value="",b&&!d&&(a.display.inputHasSelection=null));else{a.display.prevInput="",e=rf&&(g.sel.to.line-g.sel.from.line>100||(f=a.getSelection()).length>1e3);var h=e?"-":f||a.getSelection();a.display.input.value=h,a.state.focused&&Ve(a.display.input),b&&!d&&(a.display.inputHasSelection=h)}a.display.inaccurateSelection=e}function Kb(a){"nocursor"==a.options.readOnly||o&&document.activeElement==a.display.input||a.display.input.focus()}function Lb(a){return a.options.readOnly||a.doc.cantEdit}function Mb(a){function d(){a.state.focused&&setTimeout($e(Kb,a),0)}function f(){null==e&&(e=setTimeout(function(){e=null,c.cachedCharWidth=c.cachedTextHeight=lf=null,mb(a),Eb(a,$e(Fb,a))},100))}function g(){for(var a=c.wrapper.parentNode;a&&a!=document.body;a=a.parentNode);a?setTimeout(g,5e3):Ge(window,"resize",f)}function h(b){Le(a,b)||a.options.onDragEvent&&a.options.onDragEvent(a,ye(b))||Ce(b)}function i(){c.inaccurateSelection&&(c.prevInput="",c.inaccurateSelection=!1,c.input.value=a.getSelection(),Ve(c.input))}var c=a.display;Fe(c.scroller,"mousedown",Cb(a,Rb)),b?Fe(c.scroller,"dblclick",Cb(a,function(b){if(!Le(a,b)){var c=Ob(a,b);if(c&&!Sb(a,b)&&!Nb(a.display,b)){ze(b);var d=Tc(fe(a.doc,c.line).text,c);Ec(a.doc,d.from,d.to)}}})):Fe(c.scroller,"dblclick",function(b){Le(a,b)||ze(b)}),Fe(c.lineSpace,"selectstart",function(a){Nb(c,a)||ze(a)}),t||Fe(c.scroller,"contextmenu",function(b){kc(a,b)}),Fe(c.scroller,"scroll",function(){c.scroller.clientHeight&&(Wb(a,c.scroller.scrollTop),Xb(a,c.scroller.scrollLeft,!0),He(a,"scroll",a))}),Fe(c.scrollbarV,"scroll",function(){c.scroller.clientHeight&&Wb(a,c.scrollbarV.scrollTop)}),Fe(c.scrollbarH,"scroll",function(){c.scroller.clientHeight&&Xb(a,c.scrollbarH.scrollLeft)}),Fe(c.scroller,"mousewheel",function(b){$b(a,b)}),Fe(c.scroller,"DOMMouseScroll",function(b){$b(a,b)}),Fe(c.scrollbarH,"mousedown",d),Fe(c.scrollbarV,"mousedown",d),Fe(c.wrapper,"scroll",function(){c.wrapper.scrollTop=c.wrapper.scrollLeft=0});var e;Fe(window,"resize",f),setTimeout(g,5e3),Fe(c.input,"keyup",Cb(a,function(b){Le(a,b)||a.options.onKeyEvent&&a.options.onKeyEvent(a,ye(b))||16==b.keyCode&&(a.doc.sel.shift=!1)})),Fe(c.input,"input",$e(Hb,a)),Fe(c.input,"keydown",Cb(a,fc)),Fe(c.input,"keypress",Cb(a,gc)),Fe(c.input,"focus",$e(hc,a)),Fe(c.input,"blur",$e(ic,a)),a.options.dragDrop&&(Fe(c.scroller,"dragstart",function(b){Vb(a,b)}),Fe(c.scroller,"dragenter",h),Fe(c.scroller,"dragover",h),Fe(c.scroller,"drop",Cb(a,Ub))),Fe(c.scroller,"paste",function(b){Nb(c,b)||(Kb(a),Hb(a))}),Fe(c.input,"paste",function(){a.state.pasteIncoming=!0,Hb(a)}),Fe(c.input,"cut",i),Fe(c.input,"copy",i),j&&Fe(c.sizer,"mouseup",function(){document.activeElement==c.input&&c.input.blur(),Kb(a)})}function Nb(a,b){for(var c=De(b);c!=a.wrapper;c=c.parentNode)if(!c||c.ignoreEvents||c.parentNode==a.sizer&&c!=a.mover)return!0}function Ob(a,b,c){var d=a.display;if(!c){var e=De(b);if(e==d.scrollbarH||e==d.scrollbarH.firstChild||e==d.scrollbarV||e==d.scrollbarV.firstChild||e==d.scrollbarFiller||e==d.gutterFiller)return null}var f,g,h=hf(d.lineSpace);try{f=b.clientX,g=b.clientY}catch(b){return null}return ub(a,f-h.left,g-h.top)}function Rb(a){function q(a){if(!xc(p,a)){if(p=a,"single"==j)return Ec(c.doc,Bc(f,h),a),void 0;if(n=Bc(f,n),o=Bc(f,o),"double"==j){var b=Tc(fe(f,a.line).text,a);yc(a,n)?Ec(c.doc,b.from,o):Ec(c.doc,n,b.to)}else"triple"==j&&(yc(a,n)?Ec(c.doc,o,Bc(f,wc(a.line,0))):Ec(c.doc,n,Bc(f,wc(a.line+1,0))))}}function u(a){var b=++s,e=Ob(c,a,!0);if(e)if(xc(e,l)){var h=a.clientYr.bottom?20:0;h&&setTimeout(Cb(c,function(){s==b&&(d.scroller.scrollTop+=h,u(a))}),50)}else{c.state.focused||hc(c),l=e,q(e);var g=K(d,f);(e.line>=g.to||e.linei-400&&xc(Qb.pos,h))j="triple",ze(a),setTimeout($e(Kb,c),20),Uc(c,h.line);else if(Pb&&Pb.time>i-400&&xc(Pb.pos,h)){j="double",Qb={time:i,pos:h},ze(a);var k=Tc(fe(f,h.line).text,h);Ec(c.doc,k.from,k.to)}else Pb={time:i,pos:h};var l=h;if(c.options.dragDrop&&jf&&!Lb(c)&&!xc(g.from,g.to)&&!yc(h,g.from)&&!yc(g.to,h)&&"single"==j){var m=Cb(c,function(b){e&&(d.scroller.draggable=!1),c.state.draggingText=!1,Ge(document,"mouseup",m),Ge(d.scroller,"drop",m),Math.abs(a.clientX-b.clientX)+Math.abs(a.clientY-b.clientY)<10&&(ze(b),Ec(c.doc,h),Kb(c)) +});return e&&(d.scroller.draggable=!0),c.state.draggingText=m,d.scroller.dragDrop&&d.scroller.dragDrop(),Fe(document,"mouseup",m),Fe(d.scroller,"drop",m),void 0}ze(a),"single"==j&&Ec(c.doc,Bc(f,h));var n=g.from,o=g.to,p=h,r=hf(d.wrapper),s=0,w=Cb(c,function(a){b||Ee(a)?u(a):v(a)}),x=Cb(c,v);Fe(document,"mousemove",w),Fe(document,"mouseup",x)}}}function Sb(a,b){var c=a.display;try{var d=b.clientX,e=b.clientY}catch(b){return!1}if(d>=Math.floor(hf(c.gutters).right))return!1;if(ze(b),!Ne(a,"gutterClick"))return!0;var f=hf(c.lineDiv);if(e>f.bottom)return!0;e-=f.top-c.viewOffset;for(var g=0;g=d){var i=ke(a.doc,e),j=a.options.gutters[g];Ke(a,"gutterClick",a,i,j,b);break}}return!0}function Ub(a){var c=this;if(!(Le(c,a)||Nb(c.display,a)||c.options.onDragEvent&&c.options.onDragEvent(c,ye(a)))){ze(a),b&&(Tb=+new Date);var d=Ob(c,a,!0),e=a.dataTransfer.files;if(d&&!Lb(c))if(e&&e.length&&window.FileReader&&window.File)for(var f=e.length,g=Array(f),h=0,i=function(a,b){var e=new FileReader;e.onload=function(){g[b]=e.result,++h==f&&(d=Bc(c.doc,d),pc(c.doc,{from:d,to:d,text:pf(g.join("\n")),origin:"paste"},"around"))},e.readAsText(a)},j=0;f>j;++j)i(e[j],j);else{if(c.state.draggingText&&!yc(d,c.doc.sel.from)&&!yc(c.doc.sel.to,d))return c.state.draggingText(a),setTimeout($e(Kb,c),20),void 0;try{var g=a.dataTransfer.getData("Text");if(g){var k=c.doc.sel.from,l=c.doc.sel.to;Gc(c.doc,d,d),c.state.draggingText&&vc(c.doc,"",k,l,"paste"),c.replaceSelection(g,null,"paste"),Kb(c),hc(c)}}catch(a){}}}}function Vb(a,c){if(b&&(!a.state.draggingText||+new Date-Tb<100))return Ce(c),void 0;if(!Le(a,c)&&!Nb(a.display,c)){var d=a.getSelection();if(c.dataTransfer.setData("Text",d),c.dataTransfer.setDragImage&&!i){var e=df("img",null,null,"position: fixed; left: 0; top: 0;");h&&(e.width=e.height=1,a.display.wrapper.appendChild(e),e._top=e.offsetTop),c.dataTransfer.setDragImage(e,0,0),h&&e.parentNode.removeChild(e)}}}function Wb(b,c){Math.abs(b.doc.scrollTop-c)<2||(b.doc.scrollTop=c,a||P(b,[],c),b.display.scroller.scrollTop!=c&&(b.display.scroller.scrollTop=c),b.display.scrollbarV.scrollTop!=c&&(b.display.scrollbarV.scrollTop=c),a&&P(b,[]),_(b,100))}function Xb(a,b,c){(c?b==a.doc.scrollLeft:Math.abs(a.doc.scrollLeft-b)<2)||(b=Math.min(b,a.display.scroller.scrollWidth-a.display.scroller.clientWidth),a.doc.scrollLeft=b,L(a),a.display.scroller.scrollLeft!=b&&(a.display.scroller.scrollLeft=b),a.display.scrollbarH.scrollLeft!=b&&(a.display.scrollbarH.scrollLeft=b))}function $b(b,c){var d=c.wheelDeltaX,f=c.wheelDeltaY;null==d&&c.detail&&c.axis==c.HORIZONTAL_AXIS&&(d=c.detail),null==f&&c.detail&&c.axis==c.VERTICAL_AXIS?f=c.detail:null==f&&(f=c.wheelDelta);var g=b.display,i=g.scroller;if(d&&i.scrollWidth>i.clientWidth||f&&i.scrollHeight>i.clientHeight){if(f&&p&&e)for(var j=c.target;j!=i;j=j.parentNode)if(j.lineObj){b.display.currentWheelTarget=j;break}if(d&&!a&&!h&&null!=Zb)return f&&Wb(b,Math.max(0,Math.min(i.scrollTop+f*Zb,i.scrollHeight-i.clientHeight))),Xb(b,Math.max(0,Math.min(i.scrollLeft+d*Zb,i.scrollWidth-i.clientWidth))),ze(c),g.wheelStartX=null,void 0;if(f&&null!=Zb){var k=f*Zb,l=b.doc.scrollTop,m=l+g.wrapper.clientHeight;0>k?l=Math.max(0,l+k-50):m=Math.min(b.doc.height,m+k+50),P(b,[],{top:l,bottom:m})}20>Yb&&(null==g.wheelStartX?(g.wheelStartX=i.scrollLeft,g.wheelStartY=i.scrollTop,g.wheelDX=d,g.wheelDY=f,setTimeout(function(){if(null!=g.wheelStartX){var a=i.scrollLeft-g.wheelStartX,b=i.scrollTop-g.wheelStartY,c=b&&g.wheelDY&&b/g.wheelDY||a&&g.wheelDX&&a/g.wheelDX;g.wheelStartX=g.wheelStartY=null,c&&(Zb=(Zb*Yb+c)/(Yb+1),++Yb)}},200)):(g.wheelDX+=d,g.wheelDY+=f))}}function _b(a,b,c){if("string"==typeof b&&(b=dd[b],!b))return!1;a.display.pollingFast&&Ib(a)&&(a.display.pollingFast=!1);var d=a.doc,e=d.sel.shift,f=!1;try{Lb(a)&&(a.state.suppressEdits=!0),c&&(d.sel.shift=!1),f=b(a)!=Pe}finally{d.sel.shift=e,a.state.suppressEdits=!1}return f}function ac(a){var b=a.state.keyMaps.slice(0);return a.options.extraKeys&&b.push(a.options.extraKeys),b.push(a.options.keyMap),b}function cc(a,b){var c=fd(a.options.keyMap),e=c.auto;clearTimeout(bc),e&&!hd(b)&&(bc=setTimeout(function(){fd(a.options.keyMap)==c&&(a.options.keyMap=e.call?e.call(null,a):e,C(a))},50));var f=id(b,!0),g=!1;if(!f)return!1;var h=ac(a);return g=b.shiftKey?gd("Shift-"+f,h,function(b){return _b(a,b,!0)})||gd(f,h,function(b){return("string"==typeof b?/^go[A-Z]/.test(b):b.motion)?_b(a,b):void 0}):gd(f,h,function(b){return _b(a,b)}),g&&(ze(b),$(a),d&&(b.oldKeyCode=b.keyCode,b.keyCode=0),Ke(a,"keyHandled",a,f,b)),g}function dc(a,b,c){var d=gd("'"+c+"'",ac(a),function(b){return _b(a,b,!0)});return d&&(ze(b),$(a),Ke(a,"keyHandled",a,"'"+c+"'",b)),d}function fc(a){var c=this;if(c.state.focused||hc(c),b&&27==a.keyCode&&(a.returnValue=!1),!(Le(c,a)||c.options.onKeyEvent&&c.options.onKeyEvent(c,ye(a)))){var d=a.keyCode;c.doc.sel.shift=16==d||a.shiftKey;var e=cc(c,a);h&&(ec=e?d:null,!e&&88==d&&!rf&&(p?a.metaKey:a.ctrlKey)&&c.replaceSelection(""))}}function gc(a){var c=this;if(!(Le(c,a)||c.options.onKeyEvent&&c.options.onKeyEvent(c,ye(a)))){var e=a.keyCode,f=a.charCode;if(h&&e==ec)return ec=null,ze(a),void 0;if(!(h&&(!a.which||a.which<10)||j)||!cc(c,a)){var g=String.fromCharCode(null==f?e:f);this.options.electricChars&&this.doc.mode.electricChars&&this.options.smartIndent&&!Lb(this)&&this.doc.mode.electricChars.indexOf(g)>-1&&setTimeout(Cb(c,function(){Pc(c,c.doc.sel.to.line,"smart")}),75),dc(c,a,g)||(b&&!d&&(c.display.inputHasSelection=null),Hb(c))}}}function hc(a){"nocursor"!=a.options.readOnly&&(a.state.focused||(He(a,"focus",a),a.state.focused=!0,-1==a.display.wrapper.className.search(/\bCodeMirror-focused\b/)&&(a.display.wrapper.className+=" CodeMirror-focused"),Jb(a,!0)),Gb(a),$(a))}function ic(a){a.state.focused&&(He(a,"blur",a),a.state.focused=!1,a.display.wrapper.className=a.display.wrapper.className.replace(" CodeMirror-focused","")),clearInterval(a.display.blinker),setTimeout(function(){a.state.focused||(a.doc.sel.shift=!1)},150)}function kc(a,c){function k(){if(null!=e.input.selectionStart){var a=e.input.value=" "+(xc(f.from,f.to)?"":e.input.value);e.prevInput=" ",e.input.selectionStart=1,e.input.selectionEnd=a.length}}function l(){if(e.inputDiv.style.position="relative",e.input.style.cssText=j,d&&(e.scrollbarV.scrollTop=e.scroller.scrollTop=i),Gb(a),null!=e.input.selectionStart){(!b||d)&&k(),clearTimeout(jc);var c=0,f=function(){" "==e.prevInput&&0==e.input.selectionStart?Cb(a,dd.selectAll)(a):c++<10?jc=setTimeout(f,500):Jb(a)};jc=setTimeout(f,200)}}var e=a.display,f=a.doc.sel;if(!Nb(e,c)){var g=Ob(a,c),i=e.scroller.scrollTop;if(g&&!h){(xc(f.from,f.to)||yc(g,f.from)||!yc(g,f.to))&&Cb(a,Gc)(a.doc,g,g);var j=e.input.style.cssText;if(e.inputDiv.style.position="absolute",e.input.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(c.clientY-5)+"px; left: "+(c.clientX-5)+"px; z-index: 1000; background: white; outline: none;"+"border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);",Kb(a),Jb(a,!0),xc(f.from,f.to)&&(e.input.value=e.prevInput=" "),b&&!d&&k(),t){Ce(c);var m=function(){Ge(window,"mouseup",m),setTimeout(l,20)};Fe(window,"mouseup",m)}else setTimeout(l,50)}}}function mc(a,b,c){if(!yc(b.from,c))return Bc(a,c);var d=b.text.length-1-(b.to.line-b.from.line);if(c.line>b.to.line+d){var e=c.line-d,f=a.first+a.size-1;return e>f?wc(f,fe(a,f).text.length):Cc(c,fe(a,e).text.length)}if(c.line==b.to.line+d)return Cc(c,Ue(b.text).length+(1==b.text.length?b.from.ch:0)+fe(a,b.to.line).text.length-b.to.ch);var g=c.line-b.from.line;return Cc(c,b.text[g].length+(g?0:b.from.ch))}function nc(a,b,c){if(c&&"object"==typeof c)return{anchor:mc(a,b,c.anchor),head:mc(a,b,c.head)};if("start"==c)return{anchor:b.from,head:b.from};var d=lc(b);if("around"==c)return{anchor:b.from,head:d};if("end"==c)return{anchor:d,head:d};var e=function(a){if(yc(a,b.from))return a;if(!yc(b.to,a))return d;var c=a.line+b.text.length-(b.to.line-b.from.line)-1,e=a.ch;return a.line==b.to.line&&(e+=d.ch-b.to.ch),wc(c,e)};return{anchor:e(a.sel.anchor),head:e(a.sel.head)}}function oc(a,b,c){var d={canceled:!1,from:b.from,to:b.to,text:b.text,origin:b.origin,cancel:function(){this.canceled=!0}};return c&&(d.update=function(b,c,d,e){b&&(this.from=Bc(a,b)),c&&(this.to=Bc(a,c)),d&&(this.text=d),void 0!==e&&(this.origin=e)}),He(a,"beforeChange",a,d),a.cm&&He(a.cm,"beforeChange",a.cm,d),d.canceled?null:{from:d.from,to:d.to,text:d.text,origin:d.origin}}function pc(a,b,c,d){if(a.cm){if(!a.cm.curOp)return Cb(a.cm,pc)(a,b,c,d);if(a.cm.state.suppressEdits)return}if(!(Ne(a,"beforeChange")||a.cm&&Ne(a.cm,"beforeChange"))||(b=oc(a,b,!0))){var e=u&&!d&&vd(a,b.from,b.to);if(e){for(var f=e.length-1;f>=1;--f)qc(a,{from:e[f].from,to:e[f].to,text:[""]});e.length&&qc(a,{from:e[0].from,to:e[0].to,text:b.text},c)}else qc(a,b,c)}}function qc(a,b,c){var d=nc(a,b,c);qe(a,b,d,a.cm?a.cm.curOp.id:0/0),tc(a,b,d,td(a,b));var e=[];de(a,function(a,c){c||-1!=We(e,a.history)||(we(a.history,b),e.push(a.history)),tc(a,b,null,td(a,b))})}function rc(a,b){if(!a.cm||!a.cm.state.suppressEdits){var c=a.history,d=("undo"==b?c.done:c.undone).pop();if(d){var e={changes:[],anchorBefore:d.anchorAfter,headBefore:d.headAfter,anchorAfter:d.anchorBefore,headAfter:d.headBefore,generation:c.generation};("undo"==b?c.undone:c.done).push(e),c.generation=d.generation||++c.maxGeneration;for(var f=Ne(a,"beforeChange")||a.cm&&Ne(a.cm,"beforeChange"),g=d.changes.length-1;g>=0;--g){var h=d.changes[g];if(h.origin=b,f&&!oc(a,h,!1))return("undo"==b?c.done:c.undone).length=0,void 0;e.changes.push(pe(a,h));var i=g?nc(a,h,null):{anchor:d.anchorBefore,head:d.headBefore};tc(a,h,i,ud(a,h));var j=[];de(a,function(a,b){b||-1!=We(j,a.history)||(we(a.history,h),j.push(a.history)),tc(a,h,null,ud(a,h))})}}}}function sc(a,b){function c(a){return wc(a.line+b,a.ch)}a.first+=b,a.cm&&Fb(a.cm,a.first,a.first,b),a.sel.head=c(a.sel.head),a.sel.anchor=c(a.sel.anchor),a.sel.from=c(a.sel.from),a.sel.to=c(a.sel.to)}function tc(a,b,c,d){if(a.cm&&!a.cm.curOp)return Cb(a.cm,tc)(a,b,c,d);if(b.to.linea.lastLine())){if(b.from.linef&&(b={from:b.from,to:wc(f,fe(a,f).text.length),text:[b.text[0]],origin:b.origin}),b.removed=ge(a,b.from,b.to),c||(c=nc(a,b,null)),a.cm?uc(a.cm,b,d,c):Yd(a,b,d,c)}}function uc(a,b,c,d){var e=a.doc,f=a.display,g=b.from,h=b.to,i=!1,j=g.line;a.options.lineWrapping||(j=je(zd(e,fe(e,g.line))),e.iter(j,h.line+1,function(a){return a==f.maxLine?(i=!0,!0):void 0})),yc(e.sel.head,b.from)||yc(b.to,e.sel.head)||(a.curOp.cursorActivity=!0),Yd(e,b,c,d,A(a)),a.options.lineWrapping||(e.iter(j,g.line+b.text.length,function(a){var b=G(e,a);b>f.maxLineLength&&(f.maxLine=a,f.maxLineLength=b,f.maxLineChanged=!0,i=!1)}),i&&(a.curOp.updateMaxLine=!0)),e.frontier=Math.min(e.frontier,g.line),_(a,400);var k=b.text.length-(h.line-g.line)-1;if(Fb(a,g.line,h.line+1,k),Ne(a,"change")){var l={from:g,to:h,text:b.text,removed:b.removed,origin:b.origin};if(a.curOp.textChanged){for(var m=a.curOp.textChanged;m.next;m=m.next);m.next=l}else a.curOp.textChanged=l}}function vc(a,b,c,d,e){if(d||(d=c),yc(d,c)){var f=d;d=c,c=f}"string"==typeof b&&(b=pf(b)),pc(a,{from:c,to:d,text:b,origin:e},null)}function wc(a,b){return this instanceof wc?(this.line=a,this.ch=b,void 0):new wc(a,b)}function xc(a,b){return a.line==b.line&&a.ch==b.ch}function yc(a,b){return a.linec?wc(c,fe(a,c).text.length):Cc(b,fe(a,b.line).text.length)}function Cc(a,b){var c=a.ch;return null==c||c>b?wc(a.line,b):0>c?wc(a.line,0):a}function Dc(a,b){return b>=a.first&&b=f.ch:j.to>f.ch))){if(d&&(He(k,"beforeCursorEnter"),k.explicitlyCleared)){if(h.markedSpans){--i;continue}break}if(!k.atomic)continue;var l=k.find()[0>g?"from":"to"];if(xc(l,f)&&(l.ch+=g,l.ch<0?l=l.line>a.first?Bc(a,wc(l.line-1)):null:l.ch>h.text.length&&(l=l.line(window.innerHeight||document.documentElement.clientHeight)&&(e=!1),null!=e&&!m){var f="none"==c.cursor.style.display;f&&(c.cursor.style.display="",c.cursor.style.left=b.left+"px",c.cursor.style.top=b.top-c.viewOffset+"px"),c.cursor.scrollIntoView(e),f&&(c.cursor.style.display="none")}}}function Kc(a,b,c){for(null==c&&(c=0);;){var d=!1,e=sb(a,b),f=Mc(a,e.left,e.top-c,e.left,e.bottom+c),g=a.doc.scrollTop,h=a.doc.scrollLeft;if(null!=f.scrollTop&&(Wb(a,f.scrollTop),Math.abs(a.doc.scrollTop-g)>1&&(d=!0)),null!=f.scrollLeft&&(Xb(a,f.scrollLeft),Math.abs(a.doc.scrollLeft-h)>1&&(d=!0)),!d)return e}}function Lc(a,b,c,d,e){var f=Mc(a,b,c,d,e);null!=f.scrollTop&&Wb(a,f.scrollTop),null!=f.scrollLeft&&Xb(a,f.scrollLeft)}function Mc(a,b,c,d,e){var f=a.display,g=xb(a.display);0>c&&(c=0);var h=f.scroller.clientHeight-Oe,i=f.scroller.scrollTop,j={},k=a.doc.height+eb(f),l=g>c,m=e>k-g;if(i>c)j.scrollTop=l?0:c;else if(e>i+h){var n=Math.min(c,(m?k:e)-h);n!=i&&(j.scrollTop=n)}var o=f.scroller.clientWidth-Oe,p=f.scroller.scrollLeft;b+=f.gutters.offsetWidth,d+=f.gutters.offsetWidth;var q=f.gutters.offsetWidth,r=q+10>b;return p+q>b||r?(r&&(b=0),j.scrollLeft=Math.max(0,b-10-q)):d>o+p-3&&(j.scrollLeft=d+10-o),j}function Nc(a,b,c){a.curOp.updateScrollPos={scrollLeft:null==b?a.doc.scrollLeft:b,scrollTop:null==c?a.doc.scrollTop:c}}function Oc(a,b,c){var d=a.curOp.updateScrollPos||(a.curOp.updateScrollPos={scrollLeft:a.doc.scrollLeft,scrollTop:a.doc.scrollTop}),e=a.display.scroller;d.scrollTop=Math.max(0,Math.min(e.scrollHeight-e.clientHeight,d.scrollTop+c)),d.scrollLeft=Math.max(0,Math.min(e.scrollWidth-e.clientWidth,d.scrollLeft+b))}function Pc(a,b,c,d){var e=a.doc;if(null==c&&(c="add"),"smart"==c)if(a.doc.mode.indent)var f=cb(a,b);else c="prev";var k,g=a.options.tabSize,h=fe(e,b),i=Re(h.text,null,g),j=h.text.match(/^\s*/)[0];if("smart"==c&&(k=a.doc.mode.indent(f,h.text.slice(j.length),h.text),k==Pe)){if(!d)return;c="prev"}"prev"==c?k=b>e.first?Re(fe(e,b-1).text,null,g):0:"add"==c?k=i+a.options.indentUnit:"subtract"==c?k=i-a.options.indentUnit:"number"==typeof c&&(k=i+c),k=Math.max(0,k);var l="",m=0;if(a.options.indentWithTabs)for(var n=Math.floor(k/g);n;--n)m+=g,l+=" ";k>m&&(l+=Te(k-m)),l!=j&&vc(a.doc,l,wc(b,0),wc(b,j.length),"+input"),h.stateAfter=null}function Qc(a,b,c){var d=b,e=b,f=a.doc;return"number"==typeof b?e=fe(f,Ac(f,b)):d=je(b),null==d?null:c(e,d)?(Fb(a,d,d+1),e):null}function Rc(a,b,c,d,e){function k(){var b=f+c;return b=a.first+a.size?j=!1:(f=b,i=fe(a,b))}function l(a){var b=(e?Ef:Ff)(i,g,c,!0);if(null==b){if(a||!k())return j=!1;g=e?(0>c?xf:wf)(i):0>c?i.text.length:0}else g=b;return!0}var f=b.line,g=b.ch,h=c,i=fe(a,f),j=!0;if("char"==d)l();else if("column"==d)l(!0);else if("word"==d||"group"==d)for(var m=null,n="group"==d,o=!0;!(0>c)||l(!o);o=!1){var p=i.text.charAt(g)||"\n",q=af(p)?"w":n?/\s/.test(p)?null:"p":null;if(m&&m!=q){0>c&&(c=1,l());break}if(q&&(m=q),c>0&&!l(!o))break}var r=Ic(a,wc(f,g),h,!0);return j||(r.hitSide=!0),r}function Sc(a,b,c,d){var g,e=a.doc,f=b.left;if("page"==d){var h=Math.min(a.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);g=b.top+c*(h-(0>c?1.5:.5)*xb(a.display))}else"line"==d&&(g=c>0?b.bottom+3:b.top-3);for(;;){var i=ub(a,f,g);if(!i.outside)break;if(0>c?0>=g:g>=e.height){i.hitSide=!0;break}g+=5*c}return i}function Tc(a,b){var c=b.ch,d=b.ch;if(a){b.xRel<0||d==a.length?--c:++d;for(var e=a.charAt(c),f=af(e)?af:/\s/.test(e)?function(a){return/\s/.test(a)}:function(a){return!/\s/.test(a)&&!af(a)};c>0&&f(a.charAt(c-1));)--c;for(;dg;++g){var i=d(f[g]);if(i)return i}return!1}for(var e=0;e=b:f.to>b);(e||(e=[])).push({from:f.from,to:i?null:f.to,marker:g})}}return e}function sd(a,b,c){if(a)for(var e,d=0;d=b:f.to>b);if(h||"bookmark"==g.type&&f.from==b&&(!c||f.marker.insertLeft)){var i=null==f.from||(g.inclusiveLeft?f.from<=b:f.from0&&h)for(var l=0;ll;++l)o.push(q);o.push(i)}return o}function ud(a,b){var c=se(a,b),d=td(a,b);if(!c)return d;if(!d)return c;for(var e=0;eb)&&(!d||d.widtha.options.maxHighlightLength?(f=!1,i.pos=Math.min(b.length,i.start+5e4),j=null):j=c.token(i,d),f&&h==j||(gh;){var e=d[g];e>a&&d.splice(g,1,a,d[g+1],e),g+=2,h=Math.min(a,e)}if(b)if(f.opaque)d.splice(c,g-c,a,b),g=c+2;else for(;g>c;c+=2){var i=d[c+1];d[c+1]=i?i+" "+b:b}})}return d}function Nd(a,b){return b.styles&&b.styles[0]==a.state.modeGen||(b.styles=Md(a,b,b.stateAfter=cb(a,je(b)))),b.styles}function Od(a,b,c){var d=a.doc.mode,e=new jd(b.text,a.options.tabSize);for(""==b.text&&d.blankLine&&d.blankLine(c);!e.eol()&&e.pos<=a.options.maxHighlightLength;)d.token(e,c),e.start=e.pos}function Qd(a){return a?Pd[a]||(Pd[a]="cm-"+a.replace(/ +/g," cm-")):null}function Rd(a,c,d){for(var f,g=c,h=!0;f=xd(g);)g=fe(a.doc,f.find().from.line);var i={pre:df("pre"),col:0,pos:0,display:!d,measure:null,measuredSomething:!1,cm:a};g.textClass&&(i.pre.className=g.textClass);do{g.text&&(h=!1),i.measure=g==c&&d,i.pos=0,i.addToken=i.measure?Ud:Td,(b||e)&&a.getOption("lineWrapping")&&(i.addToken=Vd(i.addToken));var j=Xd(g,i,Nd(a,g));d&&g==c&&!i.measuredSomething&&(d[0]=i.pre.appendChild(of(a.display.measure)),i.measuredSomething=!0),j&&(g=fe(a.doc,j.to.line))}while(j);!d||i.measuredSomething||d[0]||(d[0]=i.pre.appendChild(h?df("span","\xa0"):of(a.display.measure))),i.pre.firstChild||Ad(a.doc,c)||i.pre.appendChild(document.createTextNode("\xa0"));var k;if(d&&b&&(k=me(g))){var l=k.length-1;k[l].from==k[l].to&&--l;var m=k[l],n=k[l-1];if(m.from+1==m.to&&n&&m.level="\ud800"&&"\udbff">i&&hi)?(null!=s.to&&l>s.to&&(l=s.to,n=""),t.className&&(m+=" "+t.className),t.startStyle&&s.from==i&&(o+=" "+t.startStyle),t.endStyle&&s.to==l&&(n+=" "+t.endStyle),t.collapsed&&(!p||p.marker.sizei&&l>s.from&&(l=s.from),"bookmark"==t.type&&s.from==i&&t.replacedWith&&(q=t.replacedWith)}if(p&&(p.from||0)==i&&(Wd(b,(null==p.to?h:p.to)-i,null!=p.from&&p.marker.replacedWith),null==p.to))return p.marker.find();q&&!p&&Wd(b,0,q)}if(i>=h)break;for(var u=Math.min(h,l);;){if(j){var v=i+j.length;if(!p){var w=v>u?j.slice(0,u-i):j;b.addToken(b,w,k?k+m:m,o,i+w.length==l?n:"")}if(v>=u){j=j.slice(u-i),i=u;break}i=v,o=""}j=e.slice(f,f=c[g++]),k=Qd(c[g++])}}else for(var g=1;gp;++p)r.push(Id(j[p],f(p),e));g(l,l.text,n),o&&a.remove(h.line,o),r.length&&a.insert(h.line,r)}else if(k==l)if(1==j.length)g(k,k.text.slice(0,h.ch)+m+k.text.slice(i.ch),n);else{for(var r=[],p=1,q=j.length-1;q>p;++p)r.push(Id(j[p],f(p),e));r.push(Id(m+k.text.slice(i.ch),n,e)),g(k,k.text.slice(0,h.ch)+j[0],f(0)),a.insert(h.line+1,r)}else if(1==j.length)g(k,k.text.slice(0,h.ch)+j[0]+l.text.slice(i.ch),f(0)),a.remove(h.line+1,o);else{g(k,k.text.slice(0,h.ch)+j[0],f(0)),g(l,m+l.text.slice(i.ch),n);for(var p=1,q=j.length-1,r=[];q>p;++p)r.push(Id(j[p],f(p),e));o>1&&a.remove(h.line+1,o-1),a.insert(h.line+1,r)}Ke(a,"change",a,b),Gc(a,d.anchor,d.head,null,!0)}function Zd(a){this.lines=a,this.parent=null;for(var b=0,c=a.length,d=0;c>b;++b)a[b].parent=this,d+=a[b].height;this.height=d}function $d(a){this.children=a;for(var b=0,c=0,d=0,e=a.length;e>d;++d){var f=a[d];b+=f.chunkSize(),c+=f.height,f.parent=this}this.size=b,this.height=c,this.parent=null}function de(a,b,c){function d(a,e,f){if(a.linked)for(var g=0;gb){a=d;break}b-=e}return a.lines[b]}function ge(a,b,c){var d=[],e=b.line;return a.iter(b.line,c.line+1,function(a){var f=a.text;e==c.line&&(f=f.slice(0,c.ch)),e==b.line&&(f=f.slice(b.ch)),d.push(f),++e}),d}function he(a,b,c){var d=[];return a.iter(b,c,function(a){d.push(a.text)}),d}function ie(a,b){for(var c=b-a.height,d=a;d;d=d.parent)d.height+=c}function je(a){if(null==a.parent)return null;for(var b=a.parent,c=We(b.lines,a),d=b.parent;d;b=d,d=d.parent)for(var e=0;d.children[e]!=b;++e)c+=d.children[e].chunkSize();return c+b.first}function ke(a,b){var c=a.first;a:do{for(var d=0,e=a.children.length;e>d;++d){var f=a.children[d],g=f.height;if(g>b){a=f;continue a}b-=g,c+=f.chunkSize()}return c}while(!a.lines);for(var d=0,e=a.lines.length;e>d;++d){var h=a.lines[d],i=h.height;if(i>b)break;b-=i}return c+d +}function le(a,b){b=zd(a.doc,b);for(var c=0,d=b.parent,e=0;ef-a.cm.options.historyEventDelay||"*"==b.origin.charAt(0)))){var h=Ue(g.changes);xc(b.from,b.to)&&xc(b.from,h.to)?h.to=lc(b):g.changes.push(pe(a,b)),g.anchorAfter=c.anchor,g.headAfter=c.head}else for(g={changes:[pe(a,b)],generation:e.generation,anchorBefore:a.sel.anchor,headBefore:a.sel.head,anchorAfter:c.anchor,headAfter:c.head},e.done.push(g),e.generation=++e.maxGeneration;e.done.length>e.undoDepth;)e.done.shift();e.lastTime=f,e.lastOp=d,e.lastOrigin=b.origin}function re(a){if(!a)return null;for(var c,b=0;b-1&&(Ue(g)[k]=i[k],delete i[k])}}return d}function ue(a,b,c,d){c0}function Qe(){this.id=null}function Re(a,b,c,d,e){null==b&&(b=a.search(/[^\s\u00a0]/),-1==b&&(b=a.length));for(var f=d||0,g=e||0;b>f;++f)" "==a.charAt(f)?g+=c-g%c:++g;return g}function Te(a){for(;Se.length<=a;)Se.push(Ue(Se)+" ");return Se[a]}function Ue(a){return a[a.length-1]}function Ve(a){if(n)a.selectionStart=0,a.selectionEnd=a.value.length;else try{a.select()}catch(b){}}function We(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;++c)if(a[c]==b)return c;return-1}function Xe(a,b){function c(){}c.prototype=a;var d=new c;return b&&Ye(b,d),d}function Ye(a,b){b||(b={});for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}function Ze(a){for(var b=[],c=0;a>c;++c)b.push(void 0);return b}function $e(a){var b=Array.prototype.slice.call(arguments,1);return function(){return a.apply(null,b)}}function af(a){return/\w/.test(a)||a>"\x80"&&(a.toUpperCase()!=a.toLowerCase()||_e.test(a))}function bf(a){for(var b in a)if(a.hasOwnProperty(b)&&a[b])return!1;return!0}function df(a,b,c,d){var e=document.createElement(a);if(c&&(e.className=c),d&&(e.style.cssText=d),"string"==typeof b)gf(e,b);else if(b)for(var f=0;f0;--b)a.removeChild(a.firstChild);return a}function ff(a,b){return ef(a).appendChild(b)}function gf(a,b){d?(a.innerHTML="",a.appendChild(document.createTextNode(b))):a.textContent=b}function hf(a){return a.getBoundingClientRect()}function kf(){return!1}function mf(a){if(null!=lf)return lf;var b=df("div",null,null,"width: 50px; height: 50px; overflow-x: scroll");return ff(a,b),b.offsetWidth&&(lf=b.offsetHeight-b.clientHeight),lf||0}function of(a){if(null==nf){var b=df("span","\u200b");ff(a,df("span",[b,document.createTextNode("x")])),0!=a.firstChild.offsetHeight&&(nf=b.offsetWidth<=1&&b.offsetHeight>2&&!c)}return nf?df("span","\u200b"):df("span","\xa0",null,"display: inline-block; width: 1px; margin-right: -1px")}function tf(a,b,c,d){if(!a)return d(b,c,"ltr");for(var e=0;eb||b==c&&f.to==b)&&d(Math.max(f.from,b),Math.min(f.to,c),1==f.level?"rtl":"ltr")}}function uf(a){return a.level%2?a.to:a.from}function vf(a){return a.level%2?a.from:a.to}function wf(a){var b=me(a);return b?uf(b[0]):0}function xf(a){var b=me(a);return b?vf(Ue(b)):a.text.length}function yf(a,b){var c=fe(a.doc,b),d=zd(a.doc,c);d!=c&&(b=je(d));var e=me(d),f=e?e[0].level%2?xf(d):wf(d):0;return wc(b,f)}function zf(a,b){for(var c,d;c=yd(d=fe(a.doc,b));)b=c.find().to.line;var e=me(d),f=e?e[0].level%2?wf(d):xf(d):d.text.length;return wc(b,f)}function Af(a,b,c){var d=a[0].level;return b==d?!0:c==d?!1:c>b}function Cf(a,b){for(var d,c=0;cb)return Bf=null,c;if(e.from==b||e.to==b){if(null!=d)return Af(a,e.level,a[d].level)?(Bf=d,c):(Bf=c,d);d=c}}return Bf=null,d}function Df(a,b,c,d){if(!d)return b+c;do b+=c;while(b>0&&cf.test(a.text.charAt(b)));return b}function Ef(a,b,c,d){var e=me(a);if(!e)return Ff(a,b,c,d);for(var f=Cf(e,b),g=e[f],h=Df(a,b,g.level%2?-c:c,d);;){if(h>g.from&&h0==g.level%2?g.to:g.from);if(g=e[f+=c],!g)return null;h=c>0==g.level%2?Df(a,g.to,-1,d):Df(a,g.from,1,d)}}function Ff(a,b,c,d){var e=b+c;if(d)for(;e>0&&cf.test(a.text.charAt(e));)e+=c;return 0>e||e>a.text.length?null:e}var a=/gecko\/\d/i.test(navigator.userAgent),b=/MSIE \d/.test(navigator.userAgent),c=b&&(null==document.documentMode||document.documentMode<8),d=b&&(null==document.documentMode||document.documentMode<9),e=/WebKit\//.test(navigator.userAgent),f=e&&/Qt\/\d+\.\d+/.test(navigator.userAgent),g=/Chrome\//.test(navigator.userAgent),h=/Opera\//.test(navigator.userAgent),i=/Apple Computer/.test(navigator.vendor),j=/KHTML\//.test(navigator.userAgent),k=/Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent),l=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent),m=/PhantomJS/.test(navigator.userAgent),n=/AppleWebKit/.test(navigator.userAgent)&&/Mobile\/\w+/.test(navigator.userAgent),o=n||/Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent),p=n||/Mac/.test(navigator.platform),q=/windows/i.test(navigator.platform),r=h&&navigator.userAgent.match(/Version\/(\d*\.\d*)/);r&&(r=Number(r[1]));var wb,Pb,Qb,s=p&&(f||h&&(null==r||12.11>r)),t=a||b&&!d,u=!1,v=!1,zb=0,Tb=0,Yb=0,Zb=null;b?Zb=-.53:a?Zb=15:g?Zb=-.7:i&&(Zb=-1/3);var bc,jc,ec=null,lc=w.changeEnd=function(a){return a.text?wc(a.from.line+a.text.length-1,Ue(a.text).length+(1==a.text.length?a.from.ch:0)):a.to};w.Pos=wc,w.prototype={constructor:w,focus:function(){window.focus(),Kb(this),hc(this),Hb(this)},setOption:function(a,b){var c=this.options,d=c[a];(c[a]!=b||"mode"==a)&&(c[a]=b,Vc.hasOwnProperty(a)&&Cb(this,Vc[a])(this,b,d))},getOption:function(a){return this.options[a]},getDoc:function(){return this.doc},addKeyMap:function(a,b){this.state.keyMaps[b?"push":"unshift"](a)},removeKeyMap:function(a){for(var b=this.state.keyMaps,c=0;c=d;++d)Pc(this,d,a)}),getTokenAt:function(a,b){var c=this.doc;a=Bc(c,a);for(var d=cb(this,a.line,b),e=this.doc.mode,f=fe(c,a.line),g=new jd(f.text,this.options.tabSize);g.pos>1;if((f?b[2*f-1]:0)>=e)d=f;else{if(!(b[2*f+1]d&&(a=d,c=!0);var e=fe(this.doc,a);return pb(this,fe(this.doc,a),{top:0,left:0},b||"page").top+(c?e.height:0)},defaultTextHeight:function(){return xb(this.display)},defaultCharWidth:function(){return yb(this.display)},setGutterMarker:Cb(null,function(a,b,c){return Qc(this,a,function(a){var d=a.gutterMarkers||(a.gutterMarkers={});return d[b]=c,!c&&bf(d)&&(a.gutterMarkers=null),!0})}),clearGutter:Cb(null,function(a){var b=this,c=b.doc,d=c.first;c.iter(function(c){c.gutterMarkers&&c.gutterMarkers[a]&&(c.gutterMarkers[a]=null,Fb(b,d,d+1),bf(c.gutterMarkers)&&(c.gutterMarkers=null)),++d})}),addLineClass:Cb(null,function(a,b,c){return Qc(this,a,function(a){var d="text"==b?"textClass":"background"==b?"bgClass":"wrapClass";if(a[d]){if(new RegExp("(?:^|\\s)"+c+"(?:$|\\s)").test(a[d]))return!1;a[d]+=" "+c}else a[d]=c;return!0})}),removeLineClass:Cb(null,function(a,b,c){return Qc(this,a,function(a){var d="text"==b?"textClass":"background"==b?"bgClass":"wrapClass",e=a[d];if(!e)return!1;if(null==c)a[d]=null;else{var f=e.match(new RegExp("(?:^|\\s+)"+c+"(?:$|\\s+)"));if(!f)return!1;var g=f.index+f[0].length;a[d]=e.slice(0,f.index)+(f.index&&g!=e.length?" ":"")+e.slice(g)||null}return!0})}),addLineWidget:Cb(null,function(a,b,c){return Hd(this,a,b,c)}),removeLineWidget:function(a){a.clear()},lineInfo:function(a){if("number"==typeof a){if(!Dc(this.doc,a))return null;var b=a;if(a=fe(this.doc,a),!a)return null}else{var b=je(a);if(null==b)return null}return{line:b,handle:a,text:a.text,gutterMarkers:a.gutterMarkers,textClass:a.textClass,bgClass:a.bgClass,wrapClass:a.wrapClass,widgets:a.widgets}},getViewport:function(){return{from:this.display.showingFrom,to:this.display.showingTo}},addWidget:function(a,b,c,d,e){var f=this.display;a=sb(this,Bc(this.doc,a));var g=a.bottom,h=a.left;if(b.style.position="absolute",f.sizer.appendChild(b),"over"==d)g=a.top;else if("above"==d||"near"==d){var i=Math.max(f.wrapper.clientHeight,this.doc.height),j=Math.max(f.sizer.clientWidth,f.lineSpace.clientWidth);("above"==d||a.bottom+b.offsetHeight>i)&&a.top>b.offsetHeight?g=a.top-b.offsetHeight:a.bottom+b.offsetHeight<=i&&(g=a.bottom),h+b.offsetWidth>j&&(h=j-b.offsetWidth)}b.style.top=g+"px",b.style.left=b.style.right="","right"==e?(h=f.sizer.clientWidth-b.offsetWidth,b.style.right="0px"):("left"==e?h=0:"middle"==e&&(h=(f.sizer.clientWidth-b.offsetWidth)/2),b.style.left=h+"px"),c&&Lc(this,h,g,h+b.offsetWidth,g+b.offsetHeight)},triggerOnKeyDown:Cb(null,fc),execCommand:function(a){return dd[a](this)},findPosH:function(a,b,c,d){var e=1;0>b&&(e=-1,b=-b);for(var f=0,g=Bc(this.doc,a);b>f&&(g=Rc(this.doc,g,e,c,d),!g.hitSide);++f);return g},moveH:Cb(null,function(a,b){var d,c=this.doc.sel;d=c.shift||c.extend||xc(c.from,c.to)?Rc(this.doc,c.head,a,b,this.options.rtlMoveVisually):0>a?c.from:c.to,Ec(this.doc,d,d,a)}),deleteH:Cb(null,function(a,b){var c=this.doc.sel;xc(c.from,c.to)?vc(this.doc,"",c.from,Rc(this.doc,c.head,a,b,!1),"+delete"):vc(this.doc,"",c.from,c.to,"+delete"),this.curOp.userSelChange=!0}),findPosV:function(a,b,c,d){var e=1,f=d;0>b&&(e=-1,b=-b);for(var g=0,h=Bc(this.doc,a);b>g;++g){var i=sb(this,h,"div");if(null==f?f=i.left:i.left=f,h=Sc(this,i,e,c),h.hitSide)break}return h},moveV:Cb(null,function(a,b){var c=this.doc.sel,d=sb(this,c.head,"div");null!=c.goalColumn&&(d.left=c.goalColumn);var e=Sc(this,d,a,b);"page"==b&&Oc(this,0,rb(this,e,"div").top-d.top),Ec(this.doc,e,e,a),c.goalColumn=d.left}),toggleOverwrite:function(a){(null==a||a!=this.state.overwrite)&&((this.state.overwrite=!this.state.overwrite)?this.display.cursor.className+=" CodeMirror-overwrite":this.display.cursor.className=this.display.cursor.className.replace(" CodeMirror-overwrite",""))},hasFocus:function(){return this.state.focused},scrollTo:Cb(null,function(a,b){Nc(this,a,b)}),getScrollInfo:function(){var a=this.display.scroller,b=Oe;return{left:a.scrollLeft,top:a.scrollTop,height:a.scrollHeight-b,width:a.scrollWidth-b,clientHeight:a.clientHeight-b,clientWidth:a.clientWidth-b}},scrollIntoView:Cb(null,function(a,b){"number"==typeof a&&(a=wc(a,0)),b||(b=0);var c=a;a&&null==a.line||(this.curOp.scrollToPos=a?Bc(this.doc,a):this.doc.sel.head,this.curOp.scrollToPosMargin=b,c=sb(this,this.curOp.scrollToPos));var d=Mc(this,c.left,c.top-b,c.right,c.bottom+b);Nc(this,d.scrollLeft,d.scrollTop)}),setSize:function(a,b){function c(a){return"number"==typeof a||/^\d+$/.test(String(a))?a+"px":a}null!=a&&(this.display.wrapper.style.width=c(a)),null!=b&&(this.display.wrapper.style.height=c(b)),this.refresh()},on:function(a,b){Fe(this,a,b)},off:function(a,b){Ge(this,a,b)},operation:function(a){return Eb(this,a)},refresh:Cb(null,function(){mb(this),Nc(this,this.doc.scrollLeft,this.doc.scrollTop),Fb(this)}),swapDoc:Cb(null,function(a){var b=this.doc;return b.cm=null,ee(this,a),mb(this),Jb(this,!0),Nc(this,a.scrollLeft,a.scrollTop),b}),getInputField:function(){return this.display.input},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}};var Vc=w.optionHandlers={},Wc=w.defaults={},Yc=w.Init={toString:function(){return"CodeMirror.Init"}};Xc("value","",function(a,b){a.setValue(b)},!0),Xc("mode",null,function(a,b){a.doc.modeOption=b,y(a)},!0),Xc("indentUnit",2,y,!0),Xc("indentWithTabs",!1),Xc("smartIndent",!0),Xc("tabSize",4,function(a){y(a),mb(a),Fb(a)},!0),Xc("electricChars",!0),Xc("rtlMoveVisually",!q),Xc("theme","default",function(a){D(a),E(a)},!0),Xc("keyMap","default",C),Xc("extraKeys",null),Xc("onKeyEvent",null),Xc("onDragEvent",null),Xc("lineWrapping",!1,z,!0),Xc("gutters",[],function(a){I(a.options),E(a)},!0),Xc("fixedGutter",!0,function(a,b){a.display.gutters.style.left=b?O(a.display)+"px":"0",a.refresh()},!0),Xc("coverGutterNextToScrollbar",!1,J,!0),Xc("lineNumbers",!1,function(a){I(a.options),E(a)},!0),Xc("firstLineNumber",1,E,!0),Xc("lineNumberFormatter",function(a){return a},E,!0),Xc("showCursorWhenSelecting",!1,X,!0),Xc("readOnly",!1,function(a,b){"nocursor"==b?(ic(a),a.display.input.blur()):b||Jb(a,!0)}),Xc("dragDrop",!0),Xc("cursorBlinkRate",530),Xc("cursorScrollMargin",0),Xc("cursorHeight",1),Xc("workTime",100),Xc("workDelay",100),Xc("flattenSpans",!0),Xc("pollInterval",100),Xc("undoDepth",40,function(a,b){a.doc.history.undoDepth=b}),Xc("historyEventDelay",500),Xc("viewportMargin",10,function(a){a.refresh()},!0),Xc("maxHighlightLength",1e4,function(a){y(a),a.refresh()},!0),Xc("moveInputWithCursor",!0,function(a,b){b||(a.display.inputDiv.style.top=a.display.inputDiv.style.left=0)}),Xc("tabindex",null,function(a,b){a.display.input.tabIndex=b||""}),Xc("autofocus",null);var Zc=w.modes={},$c=w.mimeModes={};w.defineMode=function(a,b){if(w.defaults.mode||"null"==a||(w.defaults.mode=a),arguments.length>2){b.dependencies=[];for(var c=2;c0&&b.ch=this.string.length},sol:function(){return 0==this.pos},peek:function(){return this.string.charAt(this.pos)||void 0},next:function(){return this.posb},eatSpace:function(){for(var a=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>a},skipToEnd:function(){this.pos=this.string.length},skipTo:function(a){var b=this.string.indexOf(a,this.pos);return b>-1?(this.pos=b,!0):void 0},backUp:function(a){this.pos-=a},column:function(){return this.lastColumnPos0?null:(f&&b!==!1&&(this.pos+=f[0].length),f)}var d=function(a){return c?a.toLowerCase():a},e=this.string.substr(this.pos,a.length);return d(e)==d(a)?(b!==!1&&(this.pos+=a.length),!0):void 0},current:function(){return this.string.slice(this.start,this.pos)}},w.StringStream=jd,w.TextMarker=kd,kd.prototype.clear=function(){if(!this.explicitlyCleared){var a=this.doc.cm,b=a&&!a.curOp;b&&Ab(a);for(var c=null,d=null,e=0;ea.display.maxLineLength&&(a.display.maxLine=h,a.display.maxLineLength=i,a.display.maxLineChanged=!0)}null!=c&&a&&Fb(a,c,d+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,a&&Hc(a)),b&&Bb(a),Ke(this,"clear")}},kd.prototype.find=function(){for(var a,b,c=0;c=b.display.showingFrom&&a.from.linec;++c){var e=this.lines[c];this.height-=e.height,Kd(e),Ke(e,"delete")}this.lines.splice(a,b)},collapse:function(a){a.splice.apply(a,[a.length,0].concat(this.lines))},insertInner:function(a,b,c){this.height+=c,this.lines=this.lines.slice(0,a).concat(b).concat(this.lines.slice(a));for(var d=0,e=b.length;e>d;++d)b[d].parent=this},iterN:function(a,b,c){for(var d=a+b;d>a;++a)if(c(this.lines[a]))return!0}},$d.prototype={chunkSize:function(){return this.size},removeInner:function(a,b){this.size-=b;for(var c=0;ca){var f=Math.min(b,e-a),g=d.height;if(d.removeInner(a,f),this.height-=g-d.height,e==f&&(this.children.splice(c--,1),d.parent=null),0==(b-=f))break;a=0}else a-=e}if(this.size-b<25){var h=[];this.collapse(h),this.children=[new Zd(h)],this.children[0].parent=this}},collapse:function(a){for(var b=0,c=this.children.length;c>b;++b)this.children[b].collapse(a)},insertInner:function(a,b,c){this.size+=b.length,this.height+=c;for(var d=0,e=this.children.length;e>d;++d){var f=this.children[d],g=f.chunkSize();if(g>=a){if(f.insertInner(a,b,c),f.lines&&f.lines.length>50){for(;f.lines.length>50;){var h=f.lines.splice(f.lines.length-25,25),i=new Zd(h);f.height-=i.height,this.children.splice(d+1,0,i),i.parent=this}this.maybeSpill()}break}a-=g}},maybeSpill:function(){if(!(this.children.length<=10)){var a=this;do{var b=a.children.splice(a.children.length-5,5),c=new $d(b);if(a.parent){a.size-=c.size,a.height-=c.height;var e=We(a.parent.children,a);a.parent.children.splice(e+1,0,c)}else{var d=new $d(a.children);d.parent=a,a.children=[d,c],a=d}c.parent=a.parent}while(a.children.length>10);a.parent.maybeSpill()}},iterN:function(a,b,c){for(var d=0,e=this.children.length;e>d;++d){var f=this.children[d],g=f.chunkSize();if(g>a){var h=Math.min(b,g-a);if(f.iterN(a,h,c))return!0;if(0==(b-=h))break;a=0}else a-=g}}};var _d=0,ae=w.Doc=function(a,b,c){if(!(this instanceof ae))return new ae(a,b,c);null==c&&(c=0),$d.call(this,[new Zd([Id("",null)])]),this.first=c,this.scrollTop=this.scrollLeft=0,this.cantEdit=!1,this.history=ne(),this.cleanGeneration=1,this.frontier=c;var d=wc(c,0);this.sel={from:d,to:d,head:d,anchor:d,shift:!1,extend:!1,goalColumn:null},this.id=++_d,this.modeOption=b,"string"==typeof a&&(a=pf(a)),Yd(this,{from:d,to:d,text:a},null,{head:d,anchor:d})};ae.prototype=Xe($d.prototype,{constructor:ae,iter:function(a,b,c){c?this.iterN(a-this.first,b-a,c):this.iterN(this.first,this.first+this.size,a)},insert:function(a,b){for(var c=0,d=0,e=b.length;e>d;++d)c+=b[d].height;this.insertInner(a-this.first,b,c)},remove:function(a,b){this.removeInner(a-this.first,b)},getValue:function(a){var b=he(this,this.first,this.first+this.size);return a===!1?b:b.join(a||"\n")},setValue:function(a){var b=wc(this.first,0),c=this.first+this.size-1;pc(this,{from:b,to:wc(c,fe(this,c).text.length),text:pf(a),origin:"setValue"},{head:b,anchor:b},!0)},replaceRange:function(a,b,c,d){b=Bc(this,b),c=c?Bc(this,c):b,vc(this,a,b,c,d) +},getRange:function(a,b,c){var d=ge(this,Bc(this,a),Bc(this,b));return c===!1?d:d.join(c||"\n")},getLine:function(a){var b=this.getLineHandle(a);return b&&b.text},setLine:function(a,b){Dc(this,a)&&vc(this,b,wc(a,0),Bc(this,wc(a)))},removeLine:function(a){a?vc(this,"",Bc(this,wc(a-1)),Bc(this,wc(a))):vc(this,"",wc(0,0),Bc(this,wc(1,0)))},getLineHandle:function(a){return Dc(this,a)?fe(this,a):void 0},getLineNumber:function(a){return je(a)},lineCount:function(){return this.size},firstLine:function(){return this.first},lastLine:function(){return this.first+this.size-1},clipPos:function(a){return Bc(this,a)},getCursor:function(a){var c,b=this.sel;return c=null==a||"head"==a?b.head:"anchor"==a?b.anchor:"end"==a||a===!1?b.to:b.from,zc(c)},somethingSelected:function(){return!xc(this.sel.head,this.sel.anchor)},setCursor:Db(function(a,b,c){var d=Bc(this,"number"==typeof a?wc(a,b||0):a);c?Ec(this,d):Gc(this,d,d)}),setSelection:Db(function(a,b){Gc(this,Bc(this,a),Bc(this,b||a))}),extendSelection:Db(function(a,b){Ec(this,Bc(this,a),b&&Bc(this,b))}),getSelection:function(a){return this.getRange(this.sel.from,this.sel.to,a)},replaceSelection:function(a,b,c){pc(this,{from:this.sel.from,to:this.sel.to,text:pf(a),origin:c},b||"around")},undo:Db(function(){rc(this,"undo")}),redo:Db(function(){rc(this,"redo")}),setExtending:function(a){this.sel.extend=a},historySize:function(){var a=this.history;return{undo:a.done.length,redo:a.undone.length}},clearHistory:function(){this.history=ne(this.history.maxGeneration)},markClean:function(){this.cleanGeneration=this.changeGeneration()},changeGeneration:function(){return this.history.lastOp=this.history.lastOrigin=null,this.history.generation},isClean:function(a){return this.history.generation==(a||this.cleanGeneration)},getHistory:function(){return{done:te(this.history.done),undone:te(this.history.undone)}},setHistory:function(a){var b=this.history=ne(this.history.maxGeneration);b.done=a.done.slice(0),b.undone=a.undone.slice(0)},markText:function(a,b,c){return ld(this,Bc(this,a),Bc(this,b),c,"range")},setBookmark:function(a,b){var c={replacedWith:b&&(null==b.nodeType?b.widget:b),insertLeft:b&&b.insertLeft};return a=Bc(this,a),ld(this,a,a,c,"bookmark")},findMarksAt:function(a){a=Bc(this,a);var b=[],c=fe(this,a.line).markedSpans;if(c)for(var d=0;d=a.ch)&&b.push(e.marker.parent||e.marker)}return b},getAllMarks:function(){var a=[];return this.iter(function(b){var c=b.markedSpans;if(c)for(var d=0;da?(b=a,!0):(a-=e,++c,void 0)}),Bc(this,wc(c,b))},indexFromPos:function(a){a=Bc(this,a);var b=a.ch;return a.lineb&&(b=a.from),null!=a.to&&a.to1&&45==a.charCodeAt(b-1)&&/\w/.test(a.charAt(b-2))&&/[^\-?\.]/.test(a.charAt(b))?!0:/[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|\u2026[\w~`@#$%\^&*(_=+{[><]/.test(a.slice(b-1,b+1))});var lf,nf,pf=3!="\n\nb".split(/\n/).length?function(a){for(var b=0,c=[],d=a.length;d>=b;){var e=a.indexOf("\n",b);-1==e&&(e=a.length);var f=a.slice(b,"\r"==a.charAt(e-1)?e-1:e),g=f.indexOf("\r");-1!=g?(c.push(f.slice(0,g)),b+=g+1):(c.push(f),b=e+1)}return c}:function(a){return a.split(/\r\n?|\n/)};w.splitLines=pf;var qf=window.getSelection?function(a){try{return a.selectionStart!=a.selectionEnd}catch(b){return!1}}:function(a){try{var b=a.ownerDocument.selection.createRange()}catch(c){}return b&&b.parentElement()==a?0!=b.compareEndPoints("StartToEnd",b):!1},rf=function(){var a=df("div");return"oncopy"in a?!0:(a.setAttribute("oncopy","return;"),"function"==typeof a.oncopy)}(),sf={3:"Enter",8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"PrintScrn",45:"Insert",46:"Delete",59:";",91:"Mod",92:"Mod",93:"Mod",109:"-",107:"=",127:"Delete",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",63276:"PageUp",63277:"PageDown",63275:"End",63273:"Home",63234:"Left",63232:"Up",63235:"Right",63233:"Down",63302:"Insert",63272:"Delete"};w.keyNames=sf,function(){for(var a=0;10>a;a++)sf[a+48]=String(a);for(var a=65;90>=a;a++)sf[a]=String.fromCharCode(a);for(var a=1;12>=a;a++)sf[a+111]=sf[a+63235]="F"+a}();var Bf,Gf=function(){function c(c){return 255>=c?a.charAt(c):c>=1424&&1524>=c?"R":c>=1536&&1791>=c?b.charAt(c-1536):c>=1792&&2220>=c?"r":"L"}var a="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL",b="rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr",d=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,e=/[stwN]/,f=/[LRr]/,g=/[Lb1n]/,h=/[1n]/,i="L";return function(a){if(!d.test(a))return!1;for(var l,b=a.length,j=[],k=0;b>k;++k)j.push(l=c(a.charCodeAt(k)));for(var k=0,m=i;b>k;++k){var l=j[k];"m"==l?j[k]=m:m=l}for(var k=0,n=i;b>k;++k){var l=j[k];"1"==l&&"r"==n?j[k]="n":f.test(l)&&(n=l,"r"==l&&(j[k]="R"))}for(var k=1,m=j[0];b-1>k;++k){var l=j[k];"+"==l&&"1"==m&&"1"==j[k+1]?j[k]="1":","!=l||m!=j[k+1]||"1"!=m&&"n"!=m||(j[k]=m),m=l}for(var k=0;b>k;++k){var l=j[k];if(","==l)j[k]="N";else if("%"==l){for(var o=k+1;b>o&&"%"==j[o];++o);for(var p=k&&"!"==j[k-1]||b-1>o&&"1"==j[o]?"1":"N",q=k;o>q;++q)j[q]=p;k=o-1}}for(var k=0,n=i;b>k;++k){var l=j[k];"L"==n&&"1"==l?j[k]="L":f.test(l)&&(n=l)}for(var k=0;b>k;++k)if(e.test(j[k])){for(var o=k+1;b>o&&e.test(j[o]);++o);for(var r="L"==(k?j[k-1]:i),s="L"==(b-1>o?j[o]:i),p=r||s?"L":"R",q=k;o>q;++q)j[q]=p;k=o-1}for(var u,t=[],k=0;b>k;)if(g.test(j[k])){var v=k;for(++k;b>k&&g.test(j[k]);++k);t.push({from:v,to:k,level:0})}else{var w=k,x=t.length;for(++k;b>k&&"L"!=j[k];++k);for(var q=w;k>q;)if(h.test(j[q])){q>w&&t.splice(x,0,{from:w,to:q,level:1});var y=q;for(++q;k>q&&h.test(j[q]);++q);t.splice(x,0,{from:y,to:q,level:2}),w=q}else++q;k>w&&t.splice(x,0,{from:w,to:k,level:1})}return 1==t[0].level&&(u=a.match(/^\s+/))&&(t[0].from=u[0].length,t.unshift({from:0,to:u[0].length,level:0})),1==Ue(t).level&&(u=a.match(/\s+$/))&&(Ue(t).to-=u[0].length,t.push({from:b-u[0].length,to:b,level:0})),t[0].level!=Ue(t).level&&t.push({from:b,to:b,level:t[0].level}),t}}();return w.version="3.14.0",w}(),CodeMirror.runMode=function(a,b,c,d){var e=CodeMirror.getMode(CodeMirror.defaults,b),f=/MSIE \d/.test(navigator.userAgent),g=f&&(null==document.documentMode||document.documentMode<9);if(1==c.nodeType){var h=d&&d.tabSize||CodeMirror.defaults.tabSize,i=c,j=0;i.innerHTML="",c=function(a,b){if("\n"==a)return i.appendChild(document.createTextNode(g?"\r":a)),j=0,void 0;for(var c="",d=0;;){var e=a.indexOf(" ",d);if(-1==e){c+=a.slice(d),j+=a.length-d;break}j+=e-d,c+=a.slice(d,e);var f=h-j%h;j+=f;for(var k=0;f>k;++k)c+=" ";d=e+1}if(b){var l=i.appendChild(document.createElement("span"));l.className="cm-"+b.replace(/ +/g," cm-"),l.appendChild(document.createTextNode(c))}else i.appendChild(document.createTextNode(c))}}for(var k=CodeMirror.splitLines(a),l=CodeMirror.startState(e),m=0,n=k.length;n>m;++m){m&&c("\n");for(var o=new CodeMirror.StringStream(k[m]);!o.eol();){var p=e.token(o,l);c(o.current(),p,m,o.start),o.start=o.pos}}}; diff --git a/koha-tmpl/intranet-tmpl/lib/codemirror/codemirror.js b/koha-tmpl/intranet-tmpl/lib/codemirror/codemirror.js deleted file mode 100644 index ac0f324..0000000 --- a/koha-tmpl/intranet-tmpl/lib/codemirror/codemirror.js +++ /dev/null @@ -1,5715 +0,0 @@ -// CodeMirror version 3.14 -// -// CodeMirror is the only global var we claim -window.CodeMirror = (function() { - "use strict"; - - // BROWSER SNIFFING - - // Crude, but necessary to handle a number of hard-to-feature-detect - // bugs and behavior differences. - var gecko = /gecko\/\d/i.test(navigator.userAgent); - var ie = /MSIE \d/.test(navigator.userAgent); - var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8); - var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); - var webkit = /WebKit\//.test(navigator.userAgent); - var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); - var chrome = /Chrome\//.test(navigator.userAgent); - var opera = /Opera\//.test(navigator.userAgent); - var safari = /Apple Computer/.test(navigator.vendor); - var khtml = /KHTML\//.test(navigator.userAgent); - var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent); - var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); - var phantom = /PhantomJS/.test(navigator.userAgent); - - var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); - // This is woefully incomplete. Suggestions for alternative methods welcome. - var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); - var mac = ios || /Mac/.test(navigator.platform); - var windows = /windows/i.test(navigator.platform); - - var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); - if (opera_version) opera_version = Number(opera_version[1]); - // Some browsers use the wrong event properties to signal cmd/ctrl on OS X - var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); - var captureMiddleClick = gecko || (ie && !ie_lt9); - - // Optimize some code when these features are not used - var sawReadOnlySpans = false, sawCollapsedSpans = false; - - // CONSTRUCTOR - - function CodeMirror(place, options) { - if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); - - this.options = options = options || {}; - // Determine effective options based on given values and defaults. - for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt)) - options[opt] = defaults[opt]; - setGuttersForLineNumbers(options); - - var docStart = typeof options.value == "string" ? 0 : options.value.first; - var display = this.display = makeDisplay(place, docStart); - display.wrapper.CodeMirror = this; - updateGutters(this); - if (options.autofocus && !mobile) focusInput(this); - - this.state = {keyMaps: [], - overlays: [], - modeGen: 0, - overwrite: false, focused: false, - suppressEdits: false, pasteIncoming: false, - draggingText: false, - highlight: new Delayed()}; - - themeChanged(this); - if (options.lineWrapping) - this.display.wrapper.className += " CodeMirror-wrap"; - - var doc = options.value; - if (typeof doc == "string") doc = new Doc(options.value, options.mode); - operation(this, attachDoc)(this, doc); - - // Override magic textarea content restore that IE sometimes does - // on our hidden textarea on reload - if (ie) setTimeout(bind(resetInput, this, true), 20); - - registerEventHandlers(this); - // IE throws unspecified error in certain cases, when - // trying to access activeElement before onload - var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { } - if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20); - else onBlur(this); - - operation(this, function() { - for (var opt in optionHandlers) - if (optionHandlers.propertyIsEnumerable(opt)) - optionHandlers[opt](this, options[opt], Init); - for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); - })(); - } - - // DISPLAY CONSTRUCTOR - - function makeDisplay(place, docStart) { - var d = {}; - - var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;"); - if (webkit) input.style.width = "1000px"; - else input.setAttribute("wrap", "off"); - // if border: 0; -- iOS fails to open keyboard (issue #1287) - if (ios) input.style.border = "1px solid black"; - input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false"); - - // Wraps and hides input textarea - d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); - // The actual fake scrollbars. - d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar"); - d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar"); - d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); - d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); - // DIVs containing the selection and the actual code - d.lineDiv = elt("div", null, "CodeMirror-code"); - d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); - // Blinky cursor, and element used to ensure cursor fits at the end of a line - d.cursor = elt("div", "\u00a0", "CodeMirror-cursor"); - // Secondary cursor, shown when on a 'jump' in bi-directional text - d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"); - // Used to measure text size - d.measure = elt("div", null, "CodeMirror-measure"); - // Wraps everything that needs to exist inside the vertically-padded coordinate system - d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor], - null, "position: relative; outline: none"); - // Moved around its parent to cover visible view - d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); - // Set to the height of the text, causes scrolling - d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); - // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers - d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); - // Will contain the gutters, if any - d.gutters = elt("div", null, "CodeMirror-gutters"); - d.lineGutter = null; - // Provides scrolling - d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); - d.scroller.setAttribute("tabIndex", "-1"); - // The element in which the editor lives. - d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV, - d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); - // Work around IE7 z-index bug - if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } - if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper); - - // Needed to hide big blue blinking cursor on Mobile Safari - if (ios) input.style.width = "0px"; - if (!webkit) d.scroller.draggable = true; - // Needed to handle Tab key in KHTML - if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; } - // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). - else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px"; - - // Current visible range (may be bigger than the view window). - d.viewOffset = d.lastSizeC = 0; - d.showingFrom = d.showingTo = docStart; - - // Used to only resize the line number gutter when necessary (when - // the amount of lines crosses a boundary that makes its width change) - d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; - // See readInput and resetInput - d.prevInput = ""; - // Set to true when a non-horizontal-scrolling widget is added. As - // an optimization, widget aligning is skipped when d is false. - d.alignWidgets = false; - // Flag that indicates whether we currently expect input to appear - // (after some event like 'keypress' or 'input') and are polling - // intensively. - d.pollingFast = false; - // Self-resetting timeout for the poller - d.poll = new Delayed(); - - d.cachedCharWidth = d.cachedTextHeight = null; - d.measureLineCache = []; - d.measureLineCachePos = 0; - - // Tracks when resetInput has punted to just putting a short - // string instead of the (large) selection. - d.inaccurateSelection = false; - - // Tracks the maximum line length so that the horizontal scrollbar - // can be kept static when scrolling. - d.maxLine = null; - d.maxLineLength = 0; - d.maxLineChanged = false; - - // Used for measuring wheel scrolling granularity - d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; - - return d; - } - - // STATE UPDATES - - // Used to get the editor into a consistent state again when options change. - - function loadMode(cm) { - cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); - cm.doc.iter(function(line) { - if (line.stateAfter) line.stateAfter = null; - if (line.styles) line.styles = null; - }); - cm.doc.frontier = cm.doc.first; - startWorker(cm, 100); - cm.state.modeGen++; - if (cm.curOp) regChange(cm); - } - - function wrappingChanged(cm) { - if (cm.options.lineWrapping) { - cm.display.wrapper.className += " CodeMirror-wrap"; - cm.display.sizer.style.minWidth = ""; - } else { - cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", ""); - computeMaxLength(cm); - } - estimateLineHeights(cm); - regChange(cm); - clearCaches(cm); - setTimeout(function(){updateScrollbars(cm);}, 100); - } - - function estimateHeight(cm) { - var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; - var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); - return function(line) { - if (lineIsHidden(cm.doc, line)) - return 0; - else if (wrapping) - return (Math.ceil(line.text.length / perLine) || 1) * th; - else - return th; - }; - } - - function estimateLineHeights(cm) { - var doc = cm.doc, est = estimateHeight(cm); - doc.iter(function(line) { - var estHeight = est(line); - if (estHeight != line.height) updateLineHeight(line, estHeight); - }); - } - - function keyMapChanged(cm) { - var map = keyMap[cm.options.keyMap], style = map.style; - cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + - (style ? " cm-keymap-" + style : ""); - cm.state.disableInput = map.disableInput; - } - - function themeChanged(cm) { - cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + - cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); - clearCaches(cm); - } - - function guttersChanged(cm) { - updateGutters(cm); - regChange(cm); - setTimeout(function(){alignHorizontally(cm);}, 20); - } - - function updateGutters(cm) { - var gutters = cm.display.gutters, specs = cm.options.gutters; - removeChildren(gutters); - for (var i = 0; i < specs.length; ++i) { - var gutterClass = specs[i]; - var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); - if (gutterClass == "CodeMirror-linenumbers") { - cm.display.lineGutter = gElt; - gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; - } - } - gutters.style.display = i ? "" : "none"; - } - - function lineLength(doc, line) { - if (line.height == 0) return 0; - var len = line.text.length, merged, cur = line; - while (merged = collapsedSpanAtStart(cur)) { - var found = merged.find(); - cur = getLine(doc, found.from.line); - len += found.from.ch - found.to.ch; - } - cur = line; - while (merged = collapsedSpanAtEnd(cur)) { - var found = merged.find(); - len -= cur.text.length - found.from.ch; - cur = getLine(doc, found.to.line); - len += cur.text.length - found.to.ch; - } - return len; - } - - function computeMaxLength(cm) { - var d = cm.display, doc = cm.doc; - d.maxLine = getLine(doc, doc.first); - d.maxLineLength = lineLength(doc, d.maxLine); - d.maxLineChanged = true; - doc.iter(function(line) { - var len = lineLength(doc, line); - if (len > d.maxLineLength) { - d.maxLineLength = len; - d.maxLine = line; - } - }); - } - - // Make sure the gutters options contains the element - // "CodeMirror-linenumbers" when the lineNumbers option is true. - function setGuttersForLineNumbers(options) { - var found = false; - for (var i = 0; i < options.gutters.length; ++i) { - if (options.gutters[i] == "CodeMirror-linenumbers") { - if (options.lineNumbers) found = true; - else options.gutters.splice(i--, 1); - } - } - if (!found && options.lineNumbers) - options.gutters.push("CodeMirror-linenumbers"); - } - - // SCROLLBARS - - // Re-synchronize the fake scrollbars with the actual size of the - // content. Optionally force a scrollTop. - function updateScrollbars(cm) { - var d = cm.display, docHeight = cm.doc.height; - var totalHeight = docHeight + paddingVert(d); - d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; - d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px"; - var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); - var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1); - var needsV = scrollHeight > (d.scroller.clientHeight + 1); - if (needsV) { - d.scrollbarV.style.display = "block"; - d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; - d.scrollbarV.firstChild.style.height = - (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px"; - } else d.scrollbarV.style.display = ""; - if (needsH) { - d.scrollbarH.style.display = "block"; - d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; - d.scrollbarH.firstChild.style.width = - (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px"; - } else d.scrollbarH.style.display = ""; - if (needsH && needsV) { - d.scrollbarFiller.style.display = "block"; - d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; - } else d.scrollbarFiller.style.display = ""; - if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { - d.gutterFiller.style.display = "block"; - d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px"; - d.gutterFiller.style.width = d.gutters.offsetWidth + "px"; - } else d.gutterFiller.style.display = ""; - - if (mac_geLion && scrollbarWidth(d.measure) === 0) - d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; - } - - function visibleLines(display, doc, viewPort) { - var top = display.scroller.scrollTop, height = display.wrapper.clientHeight; - if (typeof viewPort == "number") top = viewPort; - else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;} - top = Math.floor(top - paddingTop(display)); - var bottom = Math.ceil(top + height); - return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)}; - } - - // LINE NUMBERS - - function alignHorizontally(cm) { - var display = cm.display; - if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; - var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; - var gutterW = display.gutters.offsetWidth, l = comp + "px"; - for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) { - for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l; - } - if (cm.options.fixedGutter) - display.gutters.style.left = (comp + gutterW) + "px"; - } - - function maybeUpdateLineNumberWidth(cm) { - if (!cm.options.lineNumbers) return false; - var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; - if (last.length != display.lineNumChars) { - var test = display.measure.appendChild(elt("div", [elt("div", last)], - "CodeMirror-linenumber CodeMirror-gutter-elt")); - var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; - display.lineGutter.style.width = ""; - display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); - display.lineNumWidth = display.lineNumInnerWidth + padding; - display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; - display.lineGutter.style.width = display.lineNumWidth + "px"; - return true; - } - return false; - } - - function lineNumberFor(options, i) { - return String(options.lineNumberFormatter(i + options.firstLineNumber)); - } - function compensateForHScroll(display) { - return getRect(display.scroller).left - getRect(display.sizer).left; - } - - // DISPLAY DRAWING - - function updateDisplay(cm, changes, viewPort) { - var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated; - var visible = visibleLines(cm.display, cm.doc, viewPort); - for (;;) { - if (!updateDisplayInner(cm, changes, visible)) break; - updated = true; - updateSelection(cm); - updateScrollbars(cm); - - // Clip forced viewport to actual scrollable area - if (viewPort) - viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, - typeof viewPort == "number" ? viewPort : viewPort.top); - visible = visibleLines(cm.display, cm.doc, viewPort); - if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo) - break; - changes = []; - } - - if (updated) { - signalLater(cm, "update", cm); - if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) - signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); - } - return updated; - } - - // Uses a set of changes plus the current scroll position to - // determine which DOM updates have to be made, and makes the - // updates. - function updateDisplayInner(cm, changes, visible) { - var display = cm.display, doc = cm.doc; - if (!display.wrapper.clientWidth) { - display.showingFrom = display.showingTo = doc.first; - display.viewOffset = 0; - return; - } - - // Bail out if the visible area is already rendered and nothing changed. - if (changes.length == 0 && - visible.from > display.showingFrom && visible.to < display.showingTo) - return; - - if (maybeUpdateLineNumberWidth(cm)) - changes = [{from: doc.first, to: doc.first + doc.size}]; - var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px"; - display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0"; - - // Used to determine which lines need their line numbers updated - var positionsChangedFrom = Infinity; - if (cm.options.lineNumbers) - for (var i = 0; i < changes.length; ++i) - if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } - - var end = doc.first + doc.size; - var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); - var to = Math.min(end, visible.to + cm.options.viewportMargin); - if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom); - if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo); - if (sawCollapsedSpans) { - from = lineNo(visualLine(doc, getLine(doc, from))); - while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to; - } - - // Create a range of theoretically intact lines, and punch holes - // in that using the change info. - var intact = [{from: Math.max(display.showingFrom, doc.first), - to: Math.min(display.showingTo, end)}]; - if (intact[0].from >= intact[0].to) intact = []; - else intact = computeIntact(intact, changes); - // When merged lines are present, we might have to reduce the - // intact ranges because changes in continued fragments of the - // intact lines do require the lines to be redrawn. - if (sawCollapsedSpans) - for (var i = 0; i < intact.length; ++i) { - var range = intact[i], merged; - while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) { - var newTo = merged.find().from.line; - if (newTo > range.from) range.to = newTo; - else { intact.splice(i--, 1); break; } - } - } - - // Clip off the parts that won't be visible - var intactLines = 0; - for (var i = 0; i < intact.length; ++i) { - var range = intact[i]; - if (range.from < from) range.from = from; - if (range.to > to) range.to = to; - if (range.from >= range.to) intact.splice(i--, 1); - else intactLines += range.to - range.from; - } - if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) { - updateViewOffset(cm); - return; - } - intact.sort(function(a, b) {return a.from - b.from;}); - - // Avoid crashing on IE's "unspecified error" when in iframes - try { - var focused = document.activeElement; - } catch(e) {} - if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; - patchDisplay(cm, from, to, intact, positionsChangedFrom); - display.lineDiv.style.display = ""; - if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus(); - - var different = from != display.showingFrom || to != display.showingTo || - display.lastSizeC != display.wrapper.clientHeight; - // This is just a bogus formula that detects when the editor is - // resized or the font size changes. - if (different) { - display.lastSizeC = display.wrapper.clientHeight; - startWorker(cm, 400); - } - display.showingFrom = from; display.showingTo = to; - - var prevBottom = display.lineDiv.offsetTop; - for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) { - if (ie_lt8) { - var bot = node.offsetTop + node.offsetHeight; - height = bot - prevBottom; - prevBottom = bot; - } else { - var box = getRect(node); - height = box.bottom - box.top; - } - var diff = node.lineObj.height - height; - if (height < 2) height = textHeight(display); - if (diff > .001 || diff < -.001) { - updateLineHeight(node.lineObj, height); - var widgets = node.lineObj.widgets; - if (widgets) for (var i = 0; i < widgets.length; ++i) - widgets[i].height = widgets[i].node.offsetHeight; - } - } - updateViewOffset(cm); - - return true; - } - - function updateViewOffset(cm) { - var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom)); - // Position the mover div to align with the current virtual scroll position - cm.display.mover.style.top = off + "px"; - } - - function computeIntact(intact, changes) { - for (var i = 0, l = changes.length || 0; i < l; ++i) { - var change = changes[i], intact2 = [], diff = change.diff || 0; - for (var j = 0, l2 = intact.length; j < l2; ++j) { - var range = intact[j]; - if (change.to <= range.from && change.diff) { - intact2.push({from: range.from + diff, to: range.to + diff}); - } else if (change.to <= range.from || change.from >= range.to) { - intact2.push(range); - } else { - if (change.from > range.from) - intact2.push({from: range.from, to: change.from}); - if (change.to < range.to) - intact2.push({from: change.to + diff, to: range.to + diff}); - } - } - intact = intact2; - } - return intact; - } - - function getDimensions(cm) { - var d = cm.display, left = {}, width = {}; - for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { - left[cm.options.gutters[i]] = n.offsetLeft; - width[cm.options.gutters[i]] = n.offsetWidth; - } - return {fixedPos: compensateForHScroll(d), - gutterTotalWidth: d.gutters.offsetWidth, - gutterLeft: left, - gutterWidth: width, - wrapperWidth: d.wrapper.clientWidth}; - } - - function patchDisplay(cm, from, to, intact, updateNumbersFrom) { - var dims = getDimensions(cm); - var display = cm.display, lineNumbers = cm.options.lineNumbers; - if (!intact.length && (!webkit || !cm.display.currentWheelTarget)) - removeChildren(display.lineDiv); - var container = display.lineDiv, cur = container.firstChild; - - function rm(node) { - var next = node.nextSibling; - if (webkit && mac && cm.display.currentWheelTarget == node) { - node.style.display = "none"; - node.lineObj = null; - } else { - node.parentNode.removeChild(node); - } - return next; - } - - var nextIntact = intact.shift(), lineN = from; - cm.doc.iter(from, to, function(line) { - if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift(); - if (lineIsHidden(cm.doc, line)) { - if (line.height != 0) updateLineHeight(line, 0); - if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) { - var w = line.widgets[i]; - if (w.showIfHidden) { - var prev = cur.previousSibling; - if (/pre/i.test(prev.nodeName)) { - var wrap = elt("div", null, null, "position: relative"); - prev.parentNode.replaceChild(wrap, prev); - wrap.appendChild(prev); - prev = wrap; - } - var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget")); - if (!w.handleMouseEvents) wnode.ignoreEvents = true; - positionLineWidget(w, wnode, prev, dims); - } - } - } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) { - // This line is intact. Skip to the actual node. Update its - // line number if needed. - while (cur.lineObj != line) cur = rm(cur); - if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber) - setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN)); - cur = cur.nextSibling; - } else { - // For lines with widgets, make an attempt to find and reuse - // the existing element, so that widgets aren't needlessly - // removed and re-inserted into the dom - if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling) - if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; } - // This line needs to be generated. - var lineNode = buildLineElement(cm, line, lineN, dims, reuse); - if (lineNode != reuse) { - container.insertBefore(lineNode, cur); - } else { - while (cur != reuse) cur = rm(cur); - cur = cur.nextSibling; - } - - lineNode.lineObj = line; - } - ++lineN; - }); - while (cur) cur = rm(cur); - } - - function buildLineElement(cm, line, lineNo, dims, reuse) { - var lineElement = lineContent(cm, line); - var markers = line.gutterMarkers, display = cm.display, wrap; - - if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets) - return lineElement; - - // Lines with gutter elements, widgets or a background class need - // to be wrapped again, and have the extra elements added to the - // wrapper div - - if (reuse) { - reuse.alignable = null; - var isOk = true, widgetsSeen = 0, insertBefore = null; - for (var n = reuse.firstChild, next; n; n = next) { - next = n.nextSibling; - if (!/\bCodeMirror-linewidget\b/.test(n.className)) { - reuse.removeChild(n); - } else { - for (var i = 0, first = true; i < line.widgets.length; ++i) { - var widget = line.widgets[i]; - if (!widget.above) { insertBefore = n; first = false; } - if (widget.node == n.firstChild) { - positionLineWidget(widget, n, reuse, dims); - ++widgetsSeen; - break; - } - } - if (i == line.widgets.length) { isOk = false; break; } - } - } - reuse.insertBefore(lineElement, insertBefore); - if (isOk && widgetsSeen == line.widgets.length) { - wrap = reuse; - reuse.className = line.wrapClass || ""; - } - } - if (!wrap) { - wrap = elt("div", null, line.wrapClass, "position: relative"); - wrap.appendChild(lineElement); - } - // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.bgClass) - wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild); - if (cm.options.lineNumbers || markers) { - var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + - (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), - wrap.firstChild); - if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap); - if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) - wrap.lineNumber = gutterWrap.appendChild( - elt("div", lineNumberFor(cm.options, lineNo), - "CodeMirror-linenumber CodeMirror-gutter-elt", - "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " - + display.lineNumInnerWidth + "px")); - if (markers) - for (var k = 0; k < cm.options.gutters.length; ++k) { - var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; - if (found) - gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + - dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); - } - } - if (ie_lt8) wrap.style.zIndex = 2; - if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) { - var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); - if (!widget.handleMouseEvents) node.ignoreEvents = true; - positionLineWidget(widget, node, wrap, dims); - if (widget.above) - wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); - else - wrap.appendChild(node); - signalLater(widget, "redraw"); - } - return wrap; - } - - function positionLineWidget(widget, node, wrap, dims) { - if (widget.noHScroll) { - (wrap.alignable || (wrap.alignable = [])).push(node); - var width = dims.wrapperWidth; - node.style.left = dims.fixedPos + "px"; - if (!widget.coverGutter) { - width -= dims.gutterTotalWidth; - node.style.paddingLeft = dims.gutterTotalWidth + "px"; - } - node.style.width = width + "px"; - } - if (widget.coverGutter) { - node.style.zIndex = 5; - node.style.position = "relative"; - if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; - } - } - - // SELECTION / CURSOR - - function updateSelection(cm) { - var display = cm.display; - var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to); - if (collapsed || cm.options.showCursorWhenSelecting) - updateSelectionCursor(cm); - else - display.cursor.style.display = display.otherCursor.style.display = "none"; - if (!collapsed) - updateSelectionRange(cm); - else - display.selectionDiv.style.display = "none"; - - // Move the hidden textarea near the cursor to prevent scrolling artifacts - if (cm.options.moveInputWithCursor) { - var headPos = cursorCoords(cm, cm.doc.sel.head, "div"); - var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv); - display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, - headPos.top + lineOff.top - wrapOff.top)) + "px"; - display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, - headPos.left + lineOff.left - wrapOff.left)) + "px"; - } - } - - // No selection, plain cursor - function updateSelectionCursor(cm) { - var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div"); - display.cursor.style.left = pos.left + "px"; - display.cursor.style.top = pos.top + "px"; - display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; - display.cursor.style.display = ""; - - if (pos.other) { - display.otherCursor.style.display = ""; - display.otherCursor.style.left = pos.other.left + "px"; - display.otherCursor.style.top = pos.other.top + "px"; - display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; - } else { display.otherCursor.style.display = "none"; } - } - - // Highlight selection - function updateSelectionRange(cm) { - var display = cm.display, doc = cm.doc, sel = cm.doc.sel; - var fragment = document.createDocumentFragment(); - var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display); - - function add(left, top, width, bottom) { - if (top < 0) top = 0; - fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + - "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) + - "px; height: " + (bottom - top) + "px")); - } - - function drawForLine(line, fromArg, toArg) { - var lineObj = getLine(doc, line); - var lineLen = lineObj.text.length; - var start, end; - function coords(ch, bias) { - return charCoords(cm, Pos(line, ch), "div", lineObj, bias); - } - - iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { - var leftPos = coords(from, "left"), rightPos, left, right; - if (from == to) { - rightPos = leftPos; - left = right = leftPos.left; - } else { - rightPos = coords(to - 1, "right"); - if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } - left = leftPos.left; - right = rightPos.right; - } - if (fromArg == null && from == 0) left = pl; - if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part - add(left, leftPos.top, null, leftPos.bottom); - left = pl; - if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); - } - if (toArg == null && to == lineLen) right = clientWidth; - if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) - start = leftPos; - if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) - end = rightPos; - if (left < pl + 1) left = pl; - add(left, rightPos.top, right - left, rightPos.bottom); - }); - return {start: start, end: end}; - } - - if (sel.from.line == sel.to.line) { - drawForLine(sel.from.line, sel.from.ch, sel.to.ch); - } else { - var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line); - var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine); - var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end; - var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start; - if (singleVLine) { - if (leftEnd.top < rightStart.top - 2) { - add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); - add(pl, rightStart.top, rightStart.left, rightStart.bottom); - } else { - add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); - } - } - if (leftEnd.bottom < rightStart.top) - add(pl, leftEnd.bottom, null, rightStart.top); - } - - removeChildrenAndAdd(display.selectionDiv, fragment); - display.selectionDiv.style.display = ""; - } - - // Cursor-blinking - function restartBlink(cm) { - if (!cm.state.focused) return; - var display = cm.display; - clearInterval(display.blinker); - var on = true; - display.cursor.style.visibility = display.otherCursor.style.visibility = ""; - display.blinker = setInterval(function() { - display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; - }, cm.options.cursorBlinkRate); - } - - // HIGHLIGHT WORKER - - function startWorker(cm, time) { - if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo) - cm.state.highlight.set(time, bind(highlightWorker, cm)); - } - - function highlightWorker(cm) { - var doc = cm.doc; - if (doc.frontier < doc.first) doc.frontier = doc.first; - if (doc.frontier >= cm.display.showingTo) return; - var end = +new Date + cm.options.workTime; - var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); - var changed = [], prevChange; - doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) { - if (doc.frontier >= cm.display.showingFrom) { // Visible - var oldStyles = line.styles; - line.styles = highlightLine(cm, line, state); - var ischange = !oldStyles || oldStyles.length != line.styles.length; - for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; - if (ischange) { - if (prevChange && prevChange.end == doc.frontier) prevChange.end++; - else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1}); - } - line.stateAfter = copyState(doc.mode, state); - } else { - processLine(cm, line, state); - line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; - } - ++doc.frontier; - if (+new Date > end) { - startWorker(cm, cm.options.workDelay); - return true; - } - }); - if (changed.length) - operation(cm, function() { - for (var i = 0; i < changed.length; ++i) - regChange(this, changed[i].start, changed[i].end); - })(); - } - - // Finds the line to start with when starting a parse. Tries to - // find a line with a stateAfter, so that it can start with a - // valid state. If that fails, it returns the line with the - // smallest indentation, which tends to need the least context to - // parse correctly. - function findStartLine(cm, n, precise) { - var minindent, minline, doc = cm.doc; - for (var search = n, lim = n - 100; search > lim; --search) { - if (search <= doc.first) return doc.first; - var line = getLine(doc, search - 1); - if (line.stateAfter && (!precise || search <= doc.frontier)) return search; - var indented = countColumn(line.text, null, cm.options.tabSize); - if (minline == null || minindent > indented) { - minline = search - 1; - minindent = indented; - } - } - return minline; - } - - function getStateBefore(cm, n, precise) { - var doc = cm.doc, display = cm.display; - if (!doc.mode.startState) return true; - var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; - if (!state) state = startState(doc.mode); - else state = copyState(doc.mode, state); - doc.iter(pos, n, function(line) { - processLine(cm, line, state); - var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo; - line.stateAfter = save ? copyState(doc.mode, state) : null; - ++pos; - }); - return state; - } - - // POSITION MEASUREMENT - - function paddingTop(display) {return display.lineSpace.offsetTop;} - function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} - function paddingLeft(display) { - var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x")); - return e.offsetLeft; - } - - function measureChar(cm, line, ch, data, bias) { - var dir = -1; - data = data || measureLine(cm, line); - - for (var pos = ch;; pos += dir) { - var r = data[pos]; - if (r) break; - if (dir < 0 && pos == 0) dir = 1; - } - var rightV = (pos < ch || bias == "right") && r.topRight != null; - return {left: pos < ch ? r.right : r.left, - right: pos > ch ? r.left : r.right, - top: rightV ? r.topRight : r.top, - bottom: rightV ? r.bottomRight : r.bottom}; - } - - function findCachedMeasurement(cm, line) { - var cache = cm.display.measureLineCache; - for (var i = 0; i < cache.length; ++i) { - var memo = cache[i]; - if (memo.text == line.text && memo.markedSpans == line.markedSpans && - cm.display.scroller.clientWidth == memo.width && - memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass) - return memo; - } - } - - function clearCachedMeasurement(cm, line) { - var exists = findCachedMeasurement(cm, line); - if (exists) exists.text = exists.measure = exists.markedSpans = null; - } - - function measureLine(cm, line) { - // First look in the cache - var cached = findCachedMeasurement(cm, line); - if (cached) return cached.measure; - - // Failing that, recompute and store result in cache - var measure = measureLineInner(cm, line); - var cache = cm.display.measureLineCache; - var memo = {text: line.text, width: cm.display.scroller.clientWidth, - markedSpans: line.markedSpans, measure: measure, - classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass}; - if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo; - else cache.push(memo); - return measure; - } - - function measureLineInner(cm, line) { - var display = cm.display, measure = emptyArray(line.text.length); - var pre = lineContent(cm, line, measure); - - // IE does not cache element positions of inline elements between - // calls to getBoundingClientRect. This makes the loop below, - // which gathers the positions of all the characters on the line, - // do an amount of layout work quadratic to the number of - // characters. When line wrapping is off, we try to improve things - // by first subdividing the line into a bunch of inline blocks, so - // that IE can reuse most of the layout information from caches - // for those blocks. This does interfere with line wrapping, so it - // doesn't work when wrapping is on, but in that case the - // situation is slightly better, since IE does cache line-wrapping - // information and only recomputes per-line. - if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { - var fragment = document.createDocumentFragment(); - var chunk = 10, n = pre.childNodes.length; - for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) { - var wrap = elt("div", null, null, "display: inline-block"); - for (var j = 0; j < chunk && n; ++j) { - wrap.appendChild(pre.firstChild); - --n; - } - fragment.appendChild(wrap); - } - pre.appendChild(fragment); - } - - removeChildrenAndAdd(display.measure, pre); - - var outer = getRect(display.lineDiv); - var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight; - // Work around an IE7/8 bug where it will sometimes have randomly - // replaced our pre with a clone at this point. - if (ie_lt9 && display.measure.first != pre) - removeChildrenAndAdd(display.measure, pre); - - function categorizeVSpan(top, bot) { - if (bot > maxBot) bot = maxBot; - if (top < 0) top = 0; - for (var j = 0; j < vranges.length; j += 2) { - var rtop = vranges[j], rbot = vranges[j+1]; - if (rtop > bot || rbot < top) continue; - if (rtop <= top && rbot >= bot || - top <= rtop && bot >= rbot || - Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) { - vranges[j] = Math.min(top, rtop); - vranges[j+1] = Math.max(bot, rbot); - return j; - } - } - vranges.push(top, bot); - return j; - } - - for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { - var size, node = cur; - // A widget might wrap, needs special care - if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) { - if (cur.firstChild.nodeType == 1) node = cur.firstChild; - var rects = node.getClientRects(), rLeft = rects[0], rRight = rects[rects.length - 1]; - if (rects.length > 1) { - var vCatLeft = categorizeVSpan(rLeft.top - outer.top, rLeft.bottom - outer.top); - var vCatRight = categorizeVSpan(rRight.top - outer.top, rRight.bottom - outer.top); - data[i] = {left: rLeft.left - outer.left, right: rRight.right - outer.left, - top: vCatLeft, topRight: vCatRight}; - continue; - } - } - size = getRect(node); - var vCat = categorizeVSpan(size.top - outer.top, size.bottom - outer.top); - var right = size.right; - if (cur.measureRight) right = getRect(cur.measureRight).left; - data[i] = {left: size.left - outer.left, right: right - outer.left, top: vCat}; - } - for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { - var vr = cur.top, vrRight = cur.topRight; - cur.top = vranges[vr]; cur.bottom = vranges[vr+1]; - if (vrRight != null) { cur.topRight = vranges[vrRight]; cur.bottomRight = vranges[vrRight+1]; } - } - return data; - } - - function measureLineWidth(cm, line) { - var hasBadSpan = false; - if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) { - var sp = line.markedSpans[i]; - if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true; - } - var cached = !hasBadSpan && findCachedMeasurement(cm, line); - if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right; - - var pre = lineContent(cm, line); - var end = pre.appendChild(zeroWidthElement(cm.display.measure)); - removeChildrenAndAdd(cm.display.measure, pre); - return getRect(end).right - getRect(cm.display.lineDiv).left; - } - - function clearCaches(cm) { - cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; - cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; - if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; - cm.display.lineNumChars = null; - } - - function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } - function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } - - // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" - function intoCoordSystem(cm, lineObj, rect, context) { - if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { - var size = widgetHeight(lineObj.widgets[i]); - rect.top += size; rect.bottom += size; - } - if (context == "line") return rect; - if (!context) context = "local"; - var yOff = heightAtLine(cm, lineObj); - if (context == "local") yOff += paddingTop(cm.display); - else yOff -= cm.display.viewOffset; - if (context == "page" || context == "window") { - var lOff = getRect(cm.display.lineSpace); - yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); - var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); - rect.left += xOff; rect.right += xOff; - } - rect.top += yOff; rect.bottom += yOff; - return rect; - } - - // Context may be "window", "page", "div", or "local"/null - // Result is in "div" coords - function fromCoordSystem(cm, coords, context) { - if (context == "div") return coords; - var left = coords.left, top = coords.top; - // First move into "page" coordinate system - if (context == "page") { - left -= pageScrollX(); - top -= pageScrollY(); - } else if (context == "local" || !context) { - var localBox = getRect(cm.display.sizer); - left += localBox.left; - top += localBox.top; - } - - var lineSpaceBox = getRect(cm.display.lineSpace); - return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; - } - - function charCoords(cm, pos, context, lineObj, bias) { - if (!lineObj) lineObj = getLine(cm.doc, pos.line); - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context); - } - - function cursorCoords(cm, pos, context, lineObj, measurement) { - lineObj = lineObj || getLine(cm.doc, pos.line); - if (!measurement) measurement = measureLine(cm, lineObj); - function get(ch, right) { - var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left"); - if (right) m.left = m.right; else m.right = m.left; - return intoCoordSystem(cm, lineObj, m, context); - } - function getBidi(ch, partPos) { - var part = order[partPos], right = part.level % 2; - if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { - part = order[--partPos]; - ch = bidiRight(part) - (part.level % 2 ? 0 : 1); - right = true; - } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { - part = order[++partPos]; - ch = bidiLeft(part) - part.level % 2; - right = false; - } - if (right && ch == part.to && ch > part.from) return get(ch - 1); - return get(ch, right); - } - var order = getOrder(lineObj), ch = pos.ch; - if (!order) return get(ch); - var partPos = getBidiPartAt(order, ch); - var val = getBidi(ch, partPos); - if (bidiOther != null) val.other = getBidi(ch, bidiOther); - return val; - } - - function PosWithInfo(line, ch, outside, xRel) { - var pos = new Pos(line, ch); - pos.xRel = xRel; - if (outside) pos.outside = true; - return pos; - } - - // Coords must be lineSpace-local - function coordsChar(cm, x, y) { - var doc = cm.doc; - y += cm.display.viewOffset; - if (y < 0) return PosWithInfo(doc.first, 0, true, -1); - var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1; - if (lineNo > last) - return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); - if (x < 0) x = 0; - - for (;;) { - var lineObj = getLine(doc, lineNo); - var found = coordsCharInner(cm, lineObj, lineNo, x, y); - var merged = collapsedSpanAtEnd(lineObj); - var mergedPos = merged && merged.find(); - if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) - lineNo = mergedPos.to.line; - else - return found; - } - } - - function coordsCharInner(cm, lineObj, lineNo, x, y) { - var innerOff = y - heightAtLine(cm, lineObj); - var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; - var measurement = measureLine(cm, lineObj); - - function getX(ch) { - var sp = cursorCoords(cm, Pos(lineNo, ch), "line", - lineObj, measurement); - wrongLine = true; - if (innerOff > sp.bottom) return sp.left - adjust; - else if (innerOff < sp.top) return sp.left + adjust; - else wrongLine = false; - return sp.left; - } - - var bidi = getOrder(lineObj), dist = lineObj.text.length; - var from = lineLeft(lineObj), to = lineRight(lineObj); - var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; - - if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); - // Do a binary search between these bounds. - for (;;) { - if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { - var ch = x < fromX || x - fromX <= toX - x ? from : to; - var xDiff = x - (ch == from ? fromX : toX); - while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; - var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, - xDiff < 0 ? -1 : xDiff ? 1 : 0); - return pos; - } - var step = Math.ceil(dist / 2), middle = from + step; - if (bidi) { - middle = from; - for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); - } - var middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} - else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} - } - } - - var measureText; - function textHeight(display) { - if (display.cachedTextHeight != null) return display.cachedTextHeight; - if (measureText == null) { - measureText = elt("pre"); - // Measure a bunch of lines, for browsers that compute - // fractional heights. - for (var i = 0; i < 49; ++i) { - measureText.appendChild(document.createTextNode("x")); - measureText.appendChild(elt("br")); - } - measureText.appendChild(document.createTextNode("x")); - } - removeChildrenAndAdd(display.measure, measureText); - var height = measureText.offsetHeight / 50; - if (height > 3) display.cachedTextHeight = height; - removeChildren(display.measure); - return height || 1; - } - - function charWidth(display) { - if (display.cachedCharWidth != null) return display.cachedCharWidth; - var anchor = elt("span", "x"); - var pre = elt("pre", [anchor]); - removeChildrenAndAdd(display.measure, pre); - var width = anchor.offsetWidth; - if (width > 2) display.cachedCharWidth = width; - return width || 10; - } - - // OPERATIONS - - // Operations are used to wrap changes in such a way that each - // change won't have to update the cursor and display (which would - // be awkward, slow, and error-prone), but instead updates are - // batched and then all combined and executed at once. - - var nextOpId = 0; - function startOperation(cm) { - cm.curOp = { - // An array of ranges of lines that have to be updated. See - // updateDisplay. - changes: [], - updateInput: null, - userSelChange: null, - textChanged: null, - selectionChanged: false, - cursorActivity: false, - updateMaxLine: false, - updateScrollPos: false, - id: ++nextOpId - }; - if (!delayedCallbackDepth++) delayedCallbacks = []; - } - - function endOperation(cm) { - var op = cm.curOp, doc = cm.doc, display = cm.display; - cm.curOp = null; - - if (op.updateMaxLine) computeMaxLength(cm); - if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) { - var width = measureLineWidth(cm, display.maxLine); - display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px"; - display.maxLineChanged = false; - var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth); - if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos) - setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); - } - var newScrollPos, updated; - if (op.updateScrollPos) { - newScrollPos = op.updateScrollPos; - } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible - var coords = cursorCoords(cm, doc.sel.head); - newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); - } - if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null) { - updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop); - if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; - } - if (!updated && op.selectionChanged) updateSelection(cm); - if (op.updateScrollPos) { - display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop; - display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft; - alignHorizontally(cm); - if (op.scrollToPos) - scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin); - } else if (newScrollPos) { - scrollCursorIntoView(cm); - } - if (op.selectionChanged) restartBlink(cm); - - if (cm.state.focused && op.updateInput) - resetInput(cm, op.userSelChange); - - var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; - if (hidden) for (var i = 0; i < hidden.length; ++i) - if (!hidden[i].lines.length) signal(hidden[i], "hide"); - if (unhidden) for (var i = 0; i < unhidden.length; ++i) - if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); - - var delayed; - if (!--delayedCallbackDepth) { - delayed = delayedCallbacks; - delayedCallbacks = null; - } - if (op.textChanged) - signal(cm, "change", cm, op.textChanged); - if (op.cursorActivity) signal(cm, "cursorActivity", cm); - if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); - } - - // Wraps a function in an operation. Returns the wrapped function. - function operation(cm1, f) { - return function() { - var cm = cm1 || this, withOp = !cm.curOp; - if (withOp) startOperation(cm); - try { var result = f.apply(cm, arguments); } - finally { if (withOp) endOperation(cm); } - return result; - }; - } - function docOperation(f) { - return function() { - var withOp = this.cm && !this.cm.curOp, result; - if (withOp) startOperation(this.cm); - try { result = f.apply(this, arguments); } - finally { if (withOp) endOperation(this.cm); } - return result; - }; - } - function runInOp(cm, f) { - var withOp = !cm.curOp, result; - if (withOp) startOperation(cm); - try { result = f(); } - finally { if (withOp) endOperation(cm); } - return result; - } - - function regChange(cm, from, to, lendiff) { - if (from == null) from = cm.doc.first; - if (to == null) to = cm.doc.first + cm.doc.size; - cm.curOp.changes.push({from: from, to: to, diff: lendiff}); - } - - // INPUT HANDLING - - function slowPoll(cm) { - if (cm.display.pollingFast) return; - cm.display.poll.set(cm.options.pollInterval, function() { - readInput(cm); - if (cm.state.focused) slowPoll(cm); - }); - } - - function fastPoll(cm) { - var missed = false; - cm.display.pollingFast = true; - function p() { - var changed = readInput(cm); - if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} - else {cm.display.pollingFast = false; slowPoll(cm);} - } - cm.display.poll.set(20, p); - } - - // prevInput is a hack to work with IME. If we reset the textarea - // on every change, that breaks IME. So we look for changes - // compared to the previous content instead. (Modern browsers have - // events that indicate IME taking place, but these are not widely - // supported or compatible enough yet to rely on.) - function readInput(cm) { - var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false; - var text = input.value; - if (text == prevInput && posEq(sel.from, sel.to)) return false; - if (ie && !ie_lt9 && cm.display.inputHasSelection === text) { - resetInput(cm, true); - return false; - } - - var withOp = !cm.curOp; - if (withOp) startOperation(cm); - sel.shift = false; - var same = 0, l = Math.min(prevInput.length, text.length); - while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; - var from = sel.from, to = sel.to; - if (same < prevInput.length) - from = Pos(from.line, from.ch - (prevInput.length - same)); - else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming) - to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same))); - - var updateInput = cm.curOp.updateInput; - var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)), - origin: cm.state.pasteIncoming ? "paste" : "+input"}; - makeChange(cm.doc, changeEvent, "end"); - cm.curOp.updateInput = updateInput; - signalLater(cm, "inputRead", cm, changeEvent); - - if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; - else cm.display.prevInput = text; - if (withOp) endOperation(cm); - cm.state.pasteIncoming = false; - return true; - } - - function resetInput(cm, user) { - var minimal, selected, doc = cm.doc; - if (!posEq(doc.sel.from, doc.sel.to)) { - cm.display.prevInput = ""; - minimal = hasCopyEvent && - (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000); - var content = minimal ? "-" : selected || cm.getSelection(); - cm.display.input.value = content; - if (cm.state.focused) selectInput(cm.display.input); - if (ie && !ie_lt9) cm.display.inputHasSelection = content; - } else if (user) { - cm.display.prevInput = cm.display.input.value = ""; - if (ie && !ie_lt9) cm.display.inputHasSelection = null; - } - cm.display.inaccurateSelection = minimal; - } - - function focusInput(cm) { - if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input)) - cm.display.input.focus(); - } - - function isReadOnly(cm) { - return cm.options.readOnly || cm.doc.cantEdit; - } - - // EVENT HANDLERS - - function registerEventHandlers(cm) { - var d = cm.display; - on(d.scroller, "mousedown", operation(cm, onMouseDown)); - if (ie) - on(d.scroller, "dblclick", operation(cm, function(e) { - if (signalDOMEvent(cm, e)) return; - var pos = posFromMouse(cm, e); - if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; - e_preventDefault(e); - var word = findWordAt(getLine(cm.doc, pos.line).text, pos); - extendSelection(cm.doc, word.from, word.to); - })); - else - on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); - on(d.lineSpace, "selectstart", function(e) { - if (!eventInWidget(d, e)) e_preventDefault(e); - }); - // Gecko browsers fire contextmenu *after* opening the menu, at - // which point we can't mess with it anymore. Context menu is - // handled in onMouseDown for Gecko. - if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); - - on(d.scroller, "scroll", function() { - if (d.scroller.clientHeight) { - setScrollTop(cm, d.scroller.scrollTop); - setScrollLeft(cm, d.scroller.scrollLeft, true); - signal(cm, "scroll", cm); - } - }); - on(d.scrollbarV, "scroll", function() { - if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop); - }); - on(d.scrollbarH, "scroll", function() { - if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft); - }); - - on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); - on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); - - function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); } - on(d.scrollbarH, "mousedown", reFocus); - on(d.scrollbarV, "mousedown", reFocus); - // Prevent wrapper from ever scrolling - on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); - - var resizeTimer; - function onResize() { - if (resizeTimer == null) resizeTimer = setTimeout(function() { - resizeTimer = null; - // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null; - clearCaches(cm); - runInOp(cm, bind(regChange, cm)); - }, 100); - } - on(window, "resize", onResize); - // Above handler holds on to the editor and its data structures. - // Here we poll to unregister it when the editor is no longer in - // the document, so that it can be garbage-collected. - function unregister() { - for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {} - if (p) setTimeout(unregister, 5000); - else off(window, "resize", onResize); - } - setTimeout(unregister, 5000); - - on(d.input, "keyup", operation(cm, function(e) { - if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - if (e.keyCode == 16) cm.doc.sel.shift = false; - })); - on(d.input, "input", bind(fastPoll, cm)); - on(d.input, "keydown", operation(cm, onKeyDown)); - on(d.input, "keypress", operation(cm, onKeyPress)); - on(d.input, "focus", bind(onFocus, cm)); - on(d.input, "blur", bind(onBlur, cm)); - - function drag_(e) { - if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; - e_stop(e); - } - if (cm.options.dragDrop) { - on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); - on(d.scroller, "dragenter", drag_); - on(d.scroller, "dragover", drag_); - on(d.scroller, "drop", operation(cm, onDrop)); - } - on(d.scroller, "paste", function(e){ - if (eventInWidget(d, e)) return; - focusInput(cm); - fastPoll(cm); - }); - on(d.input, "paste", function() { - cm.state.pasteIncoming = true; - fastPoll(cm); - }); - - function prepareCopy() { - if (d.inaccurateSelection) { - d.prevInput = ""; - d.inaccurateSelection = false; - d.input.value = cm.getSelection(); - selectInput(d.input); - } - } - on(d.input, "cut", prepareCopy); - on(d.input, "copy", prepareCopy); - - // Needed to handle Tab key in KHTML - if (khtml) on(d.sizer, "mouseup", function() { - if (document.activeElement == d.input) d.input.blur(); - focusInput(cm); - }); - } - - function eventInWidget(display, e) { - for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { - if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true; - } - } - - function posFromMouse(cm, e, liberal) { - var display = cm.display; - if (!liberal) { - var target = e_target(e); - if (target == display.scrollbarH || target == display.scrollbarH.firstChild || - target == display.scrollbarV || target == display.scrollbarV.firstChild || - target == display.scrollbarFiller || target == display.gutterFiller) return null; - } - var x, y, space = getRect(display.lineSpace); - // Fails unpredictably on IE[67] when mouse is dragged around quickly. - try { x = e.clientX; y = e.clientY; } catch (e) { return null; } - return coordsChar(cm, x - space.left, y - space.top); - } - - var lastClick, lastDoubleClick; - function onMouseDown(e) { - if (signalDOMEvent(this, e)) return; - var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel; - sel.shift = e.shiftKey; - - if (eventInWidget(display, e)) { - if (!webkit) { - display.scroller.draggable = false; - setTimeout(function(){display.scroller.draggable = true;}, 100); - } - return; - } - if (clickInGutter(cm, e)) return; - var start = posFromMouse(cm, e); - - switch (e_button(e)) { - case 3: - if (captureMiddleClick) onContextMenu.call(cm, cm, e); - return; - case 2: - if (start) extendSelection(cm.doc, start); - setTimeout(bind(focusInput, cm), 20); - e_preventDefault(e); - return; - } - // For button 1, if it was clicked inside the editor - // (posFromMouse returning non-null), we have to adjust the - // selection. - if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;} - - if (!cm.state.focused) onFocus(cm); - - var now = +new Date, type = "single"; - if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { - type = "triple"; - e_preventDefault(e); - setTimeout(bind(focusInput, cm), 20); - selectLine(cm, start.line); - } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { - type = "double"; - lastDoubleClick = {time: now, pos: start}; - e_preventDefault(e); - var word = findWordAt(getLine(doc, start.line).text, start); - extendSelection(cm.doc, word.from, word.to); - } else { lastClick = {time: now, pos: start}; } - - var last = start; - if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) && - !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { - var dragEnd = operation(cm, function(e2) { - if (webkit) display.scroller.draggable = false; - cm.state.draggingText = false; - off(document, "mouseup", dragEnd); - off(display.scroller, "drop", dragEnd); - if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { - e_preventDefault(e2); - extendSelection(cm.doc, start); - focusInput(cm); - } - }); - // Let the drag handler handle this. - if (webkit) display.scroller.draggable = true; - cm.state.draggingText = dragEnd; - // IE's approach to draggable - if (display.scroller.dragDrop) display.scroller.dragDrop(); - on(document, "mouseup", dragEnd); - on(display.scroller, "drop", dragEnd); - return; - } - e_preventDefault(e); - if (type == "single") extendSelection(cm.doc, clipPos(doc, start)); - - var startstart = sel.from, startend = sel.to, lastPos = start; - - function doSelect(cur) { - if (posEq(lastPos, cur)) return; - lastPos = cur; - - if (type == "single") { - extendSelection(cm.doc, clipPos(doc, start), cur); - return; - } - - startstart = clipPos(doc, startstart); - startend = clipPos(doc, startend); - if (type == "double") { - var word = findWordAt(getLine(doc, cur.line).text, cur); - if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend); - else extendSelection(cm.doc, startstart, word.to); - } else if (type == "triple") { - if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0))); - else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0))); - } - } - - var editorSize = getRect(display.wrapper); - // Used to ensure timeout re-tries don't fire when another extend - // happened in the meantime (clearTimeout isn't reliable -- at - // least on Chrome, the timeouts still happen even when cleared, - // if the clear happens after their scheduled firing time). - var counter = 0; - - function extend(e) { - var curCount = ++counter; - var cur = posFromMouse(cm, e, true); - if (!cur) return; - if (!posEq(cur, last)) { - if (!cm.state.focused) onFocus(cm); - last = cur; - doSelect(cur); - var visible = visibleLines(display, doc); - if (cur.line >= visible.to || cur.line < visible.from) - setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); - } else { - var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; - if (outside) setTimeout(operation(cm, function() { - if (counter != curCount) return; - display.scroller.scrollTop += outside; - extend(e); - }), 50); - } - } - - function done(e) { - counter = Infinity; - e_preventDefault(e); - focusInput(cm); - off(document, "mousemove", move); - off(document, "mouseup", up); - } - - var move = operation(cm, function(e) { - if (!ie && !e_button(e)) done(e); - else extend(e); - }); - var up = operation(cm, done); - on(document, "mousemove", move); - on(document, "mouseup", up); - } - - function clickInGutter(cm, e) { - var display = cm.display; - try { var mX = e.clientX, mY = e.clientY; } - catch(e) { return false; } - - if (mX >= Math.floor(getRect(display.gutters).right)) return false; - e_preventDefault(e); - if (!hasHandler(cm, "gutterClick")) return true; - - var lineBox = getRect(display.lineDiv); - if (mY > lineBox.bottom) return true; - mY -= lineBox.top - display.viewOffset; - - for (var i = 0; i < cm.options.gutters.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && getRect(g).right >= mX) { - var line = lineAtHeight(cm.doc, mY); - var gutter = cm.options.gutters[i]; - signalLater(cm, "gutterClick", cm, line, gutter, e); - break; - } - } - return true; - } - - // Kludge to work around strange IE behavior where it'll sometimes - // re-fire a series of drag-related events right after the drop (#1551) - var lastDrop = 0; - - function onDrop(e) { - var cm = this; - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) - return; - e_preventDefault(e); - if (ie) lastDrop = +new Date; - var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; - if (!pos || isReadOnly(cm)) return; - if (files && files.length && window.FileReader && window.File) { - var n = files.length, text = Array(n), read = 0; - var loadFile = function(file, i) { - var reader = new FileReader; - reader.onload = function() { - text[i] = reader.result; - if (++read == n) { - pos = clipPos(cm.doc, pos); - makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around"); - } - }; - reader.readAsText(file); - }; - for (var i = 0; i < n; ++i) loadFile(files[i], i); - } else { - // Don't do a replace if the drop happened inside of the selected text. - if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) { - cm.state.draggingText(e); - // Ensure the editor is re-focused - setTimeout(bind(focusInput, cm), 20); - return; - } - try { - var text = e.dataTransfer.getData("Text"); - if (text) { - var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to; - setSelection(cm.doc, pos, pos); - if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste"); - cm.replaceSelection(text, null, "paste"); - focusInput(cm); - onFocus(cm); - } - } - catch(e){} - } - } - - function onDragStart(cm, e) { - if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; - - var txt = cm.getSelection(); - e.dataTransfer.setData("Text", txt); - - // Use dummy image instead of default browsers image. - // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. - if (e.dataTransfer.setDragImage && !safari) { - var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); - if (opera) { - img.width = img.height = 1; - cm.display.wrapper.appendChild(img); - // Force a relayout, or Opera won't use our image for some obscure reason - img._top = img.offsetTop; - } - e.dataTransfer.setDragImage(img, 0, 0); - if (opera) img.parentNode.removeChild(img); - } - } - - function setScrollTop(cm, val) { - if (Math.abs(cm.doc.scrollTop - val) < 2) return; - cm.doc.scrollTop = val; - if (!gecko) updateDisplay(cm, [], val); - if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; - if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; - if (gecko) updateDisplay(cm, []); - startWorker(cm, 100); - } - function setScrollLeft(cm, val, isScroller) { - if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; - val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); - cm.doc.scrollLeft = val; - alignHorizontally(cm); - if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; - if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val; - } - - // Since the delta values reported on mouse wheel events are - // unstandardized between browsers and even browser versions, and - // generally horribly unpredictable, this code starts by measuring - // the scroll effect that the first few mouse wheel events have, - // and, from that, detects the way it can convert deltas to pixel - // offsets afterwards. - // - // The reason we want to know the amount a wheel event will scroll - // is that it gives us a chance to update the display before the - // actual scrolling happens, reducing flickering. - - var wheelSamples = 0, wheelPixelsPerUnit = null; - // Fill in a browser-detected starting value on browsers where we - // know one. These don't have to be accurate -- the result of them - // being wrong would just be a slight flicker on the first wheel - // scroll (if it is large enough). - if (ie) wheelPixelsPerUnit = -.53; - else if (gecko) wheelPixelsPerUnit = 15; - else if (chrome) wheelPixelsPerUnit = -.7; - else if (safari) wheelPixelsPerUnit = -1/3; - - function onScrollWheel(cm, e) { - var dx = e.wheelDeltaX, dy = e.wheelDeltaY; - if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; - if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; - else if (dy == null) dy = e.wheelDelta; - - var display = cm.display, scroll = display.scroller; - // Quit if there's nothing to scroll here - if (!(dx && scroll.scrollWidth > scroll.clientWidth || - dy && scroll.scrollHeight > scroll.clientHeight)) return; - - // Webkit browsers on OS X abort momentum scrolls when the target - // of the scroll event is removed from the scrollable element. - // This hack (see related code in patchDisplay) makes sure the - // element is kept around. - if (dy && mac && webkit) { - for (var cur = e.target; cur != scroll; cur = cur.parentNode) { - if (cur.lineObj) { - cm.display.currentWheelTarget = cur; - break; - } - } - } - - // On some browsers, horizontal scrolling will cause redraws to - // happen before the gutter has been realigned, causing it to - // wriggle around in a most unseemly way. When we have an - // estimated pixels/delta value, we just handle horizontal - // scrolling entirely here. It'll be slightly off from native, but - // better than glitching out. - if (dx && !gecko && !opera && wheelPixelsPerUnit != null) { - if (dy) - setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); - setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); - e_preventDefault(e); - display.wheelStartX = null; // Abort measurement, if in progress - return; - } - - if (dy && wheelPixelsPerUnit != null) { - var pixels = dy * wheelPixelsPerUnit; - var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; - if (pixels < 0) top = Math.max(0, top + pixels - 50); - else bot = Math.min(cm.doc.height, bot + pixels + 50); - updateDisplay(cm, [], {top: top, bottom: bot}); - } - - if (wheelSamples < 20) { - if (display.wheelStartX == null) { - display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; - display.wheelDX = dx; display.wheelDY = dy; - setTimeout(function() { - if (display.wheelStartX == null) return; - var movedX = scroll.scrollLeft - display.wheelStartX; - var movedY = scroll.scrollTop - display.wheelStartY; - var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || - (movedX && display.wheelDX && movedX / display.wheelDX); - display.wheelStartX = display.wheelStartY = null; - if (!sample) return; - wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); - ++wheelSamples; - }, 200); - } else { - display.wheelDX += dx; display.wheelDY += dy; - } - } - } - - function doHandleBinding(cm, bound, dropShift) { - if (typeof bound == "string") { - bound = commands[bound]; - if (!bound) return false; - } - // Ensure previous input has been read, so that the handler sees a - // consistent view of the document - if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false; - var doc = cm.doc, prevShift = doc.sel.shift, done = false; - try { - if (isReadOnly(cm)) cm.state.suppressEdits = true; - if (dropShift) doc.sel.shift = false; - done = bound(cm) != Pass; - } finally { - doc.sel.shift = prevShift; - cm.state.suppressEdits = false; - } - return done; - } - - function allKeyMaps(cm) { - var maps = cm.state.keyMaps.slice(0); - if (cm.options.extraKeys) maps.push(cm.options.extraKeys); - maps.push(cm.options.keyMap); - return maps; - } - - var maybeTransition; - function handleKeyBinding(cm, e) { - // Handle auto keymap transitions - var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; - clearTimeout(maybeTransition); - if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { - if (getKeyMap(cm.options.keyMap) == startMap) { - cm.options.keyMap = (next.call ? next.call(null, cm) : next); - keyMapChanged(cm); - } - }, 50); - - var name = keyName(e, true), handled = false; - if (!name) return false; - var keymaps = allKeyMaps(cm); - - if (e.shiftKey) { - // First try to resolve full name (including 'Shift-'). Failing - // that, see if there is a cursor-motion command (starting with - // 'go') bound to the keyname without 'Shift-'. - handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);}) - || lookupKey(name, keymaps, function(b) { - if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) - return doHandleBinding(cm, b); - }); - } else { - handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); - } - - if (handled) { - e_preventDefault(e); - restartBlink(cm); - if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } - signalLater(cm, "keyHandled", cm, name, e); - } - return handled; - } - - function handleCharBinding(cm, e, ch) { - var handled = lookupKey("'" + ch + "'", allKeyMaps(cm), - function(b) { return doHandleBinding(cm, b, true); }); - if (handled) { - e_preventDefault(e); - restartBlink(cm); - signalLater(cm, "keyHandled", cm, "'" + ch + "'", e); - } - return handled; - } - - var lastStoppedKey = null; - function onKeyDown(e) { - var cm = this; - if (!cm.state.focused) onFocus(cm); - if (ie && e.keyCode == 27) { e.returnValue = false; } - if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - var code = e.keyCode; - // IE does strange things with escape. - cm.doc.sel.shift = code == 16 || e.shiftKey; - // First give onKeyEvent option a chance to handle this. - var handled = handleKeyBinding(cm, e); - if (opera) { - lastStoppedKey = handled ? code : null; - // Opera has no cut event... we try to at least catch the key combo - if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) - cm.replaceSelection(""); - } - } - - function onKeyPress(e) { - var cm = this; - if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - var keyCode = e.keyCode, charCode = e.charCode; - if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} - if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; - var ch = String.fromCharCode(charCode == null ? keyCode : charCode); - if (this.options.electricChars && this.doc.mode.electricChars && - this.options.smartIndent && !isReadOnly(this) && - this.doc.mode.electricChars.indexOf(ch) > -1) - setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75); - if (handleCharBinding(cm, e, ch)) return; - if (ie && !ie_lt9) cm.display.inputHasSelection = null; - fastPoll(cm); - } - - function onFocus(cm) { - if (cm.options.readOnly == "nocursor") return; - if (!cm.state.focused) { - signal(cm, "focus", cm); - cm.state.focused = true; - if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1) - cm.display.wrapper.className += " CodeMirror-focused"; - resetInput(cm, true); - } - slowPoll(cm); - restartBlink(cm); - } - function onBlur(cm) { - if (cm.state.focused) { - signal(cm, "blur", cm); - cm.state.focused = false; - cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", ""); - } - clearInterval(cm.display.blinker); - setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150); - } - - var detectingSelectAll; - function onContextMenu(cm, e) { - var display = cm.display, sel = cm.doc.sel; - if (eventInWidget(display, e)) return; - - var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; - if (!pos || opera) return; // Opera is difficult. - if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) - operation(cm, setSelection)(cm.doc, pos, pos); - - var oldCSS = display.input.style.cssText; - display.inputDiv.style.position = "absolute"; - display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + - "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" + - "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);"; - focusInput(cm); - resetInput(cm, true); - // Adds "Select all" to context menu in FF - if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " "; - - function prepareSelectAllHack() { - if (display.input.selectionStart != null) { - var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value); - display.prevInput = " "; - display.input.selectionStart = 1; display.input.selectionEnd = extval.length; - } - } - function rehide() { - display.inputDiv.style.position = "relative"; - display.input.style.cssText = oldCSS; - if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; - slowPoll(cm); - - // Try to detect the user choosing select-all - if (display.input.selectionStart != null) { - if (!ie || ie_lt9) prepareSelectAllHack(); - clearTimeout(detectingSelectAll); - var i = 0, poll = function(){ - if (display.prevInput == " " && display.input.selectionStart == 0) - operation(cm, commands.selectAll)(cm); - else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); - else resetInput(cm); - }; - detectingSelectAll = setTimeout(poll, 200); - } - } - - if (ie && !ie_lt9) prepareSelectAllHack(); - if (captureMiddleClick) { - e_stop(e); - var mouseup = function() { - off(window, "mouseup", mouseup); - setTimeout(rehide, 20); - }; - on(window, "mouseup", mouseup); - } else { - setTimeout(rehide, 50); - } - } - - // UPDATING - - var changeEnd = CodeMirror.changeEnd = function(change) { - if (!change.text) return change.to; - return Pos(change.from.line + change.text.length - 1, - lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); - }; - - // Make sure a position will be valid after the given change. - function clipPostChange(doc, change, pos) { - if (!posLess(change.from, pos)) return clipPos(doc, pos); - var diff = (change.text.length - 1) - (change.to.line - change.from.line); - if (pos.line > change.to.line + diff) { - var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1; - if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length); - return clipToLen(pos, getLine(doc, preLine).text.length); - } - if (pos.line == change.to.line + diff) - return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) + - getLine(doc, change.to.line).text.length - change.to.ch); - var inside = pos.line - change.from.line; - return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch)); - } - - // Hint can be null|"end"|"start"|"around"|{anchor,head} - function computeSelAfterChange(doc, change, hint) { - if (hint && typeof hint == "object") // Assumed to be {anchor, head} object - return {anchor: clipPostChange(doc, change, hint.anchor), - head: clipPostChange(doc, change, hint.head)}; - - if (hint == "start") return {anchor: change.from, head: change.from}; - - var end = changeEnd(change); - if (hint == "around") return {anchor: change.from, head: end}; - if (hint == "end") return {anchor: end, head: end}; - - // hint is null, leave the selection alone as much as possible - var adjustPos = function(pos) { - if (posLess(pos, change.from)) return pos; - if (!posLess(change.to, pos)) return end; - - var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; - if (pos.line == change.to.line) ch += end.ch - change.to.ch; - return Pos(line, ch); - }; - return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)}; - } - - function filterChange(doc, change, update) { - var obj = { - canceled: false, - from: change.from, - to: change.to, - text: change.text, - origin: change.origin, - cancel: function() { this.canceled = true; } - }; - if (update) obj.update = function(from, to, text, origin) { - if (from) this.from = clipPos(doc, from); - if (to) this.to = clipPos(doc, to); - if (text) this.text = text; - if (origin !== undefined) this.origin = origin; - }; - signal(doc, "beforeChange", doc, obj); - if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); - - if (obj.canceled) return null; - return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; - } - - // Replace the range from from to to by the strings in replacement. - // change is a {from, to, text [, origin]} object - function makeChange(doc, change, selUpdate, ignoreReadOnly) { - if (doc.cm) { - if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly); - if (doc.cm.state.suppressEdits) return; - } - - if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { - change = filterChange(doc, change, true); - if (!change) return; - } - - // Possibly split or suppress the update based on the presence - // of read-only spans in its range. - var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); - if (split) { - for (var i = split.length - 1; i >= 1; --i) - makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]}); - if (split.length) - makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate); - } else { - makeChangeNoReadonly(doc, change, selUpdate); - } - } - - function makeChangeNoReadonly(doc, change, selUpdate) { - var selAfter = computeSelAfterChange(doc, change, selUpdate); - addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); - - makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); - var rebased = []; - - linkedDocs(doc, function(doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); - }); - } - - function makeChangeFromHistory(doc, type) { - if (doc.cm && doc.cm.state.suppressEdits) return; - - var hist = doc.history; - var event = (type == "undo" ? hist.done : hist.undone).pop(); - if (!event) return; - - var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter, - anchorAfter: event.anchorBefore, headAfter: event.headBefore, - generation: hist.generation}; - (type == "undo" ? hist.undone : hist.done).push(anti); - hist.generation = event.generation || ++hist.maxGeneration; - - var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); - - for (var i = event.changes.length - 1; i >= 0; --i) { - var change = event.changes[i]; - change.origin = type; - if (filter && !filterChange(doc, change, false)) { - (type == "undo" ? hist.done : hist.undone).length = 0; - return; - } - - anti.changes.push(historyChangeFromChange(doc, change)); - - var after = i ? computeSelAfterChange(doc, change, null) - : {anchor: event.anchorBefore, head: event.headBefore}; - makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); - var rebased = []; - - linkedDocs(doc, function(doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); - }); - } - } - - function shiftDoc(doc, distance) { - function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);} - doc.first += distance; - if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance); - doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor); - doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to); - } - - function makeChangeSingleDoc(doc, change, selAfter, spans) { - if (doc.cm && !doc.cm.curOp) - return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); - - if (change.to.line < doc.first) { - shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); - return; - } - if (change.from.line > doc.lastLine()) return; - - // Clip the change to the size of this doc - if (change.from.line < doc.first) { - var shift = change.text.length - 1 - (doc.first - change.from.line); - shiftDoc(doc, shift); - change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), - text: [lst(change.text)], origin: change.origin}; - } - var last = doc.lastLine(); - if (change.to.line > last) { - change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), - text: [change.text[0]], origin: change.origin}; - } - - change.removed = getBetween(doc, change.from, change.to); - - if (!selAfter) selAfter = computeSelAfterChange(doc, change, null); - if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter); - else updateDoc(doc, change, spans, selAfter); - } - - function makeChangeSingleDocInEditor(cm, change, spans, selAfter) { - var doc = cm.doc, display = cm.display, from = change.from, to = change.to; - - var recomputeMaxLength = false, checkWidthStart = from.line; - if (!cm.options.lineWrapping) { - checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line))); - doc.iter(checkWidthStart, to.line + 1, function(line) { - if (line == display.maxLine) { - recomputeMaxLength = true; - return true; - } - }); - } - - if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head)) - cm.curOp.cursorActivity = true; - - updateDoc(doc, change, spans, selAfter, estimateHeight(cm)); - - if (!cm.options.lineWrapping) { - doc.iter(checkWidthStart, from.line + change.text.length, function(line) { - var len = lineLength(doc, line); - if (len > display.maxLineLength) { - display.maxLine = line; - display.maxLineLength = len; - display.maxLineChanged = true; - recomputeMaxLength = false; - } - }); - if (recomputeMaxLength) cm.curOp.updateMaxLine = true; - } - - // Adjust frontier, schedule worker - doc.frontier = Math.min(doc.frontier, from.line); - startWorker(cm, 400); - - var lendiff = change.text.length - (to.line - from.line) - 1; - // Remember that these lines changed, for updating the display - regChange(cm, from.line, to.line + 1, lendiff); - - if (hasHandler(cm, "change")) { - var changeObj = {from: from, to: to, - text: change.text, - removed: change.removed, - origin: change.origin}; - if (cm.curOp.textChanged) { - for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {} - cur.next = changeObj; - } else cm.curOp.textChanged = changeObj; - } - } - - function replaceRange(doc, code, from, to, origin) { - if (!to) to = from; - if (posLess(to, from)) { var tmp = to; to = from; from = tmp; } - if (typeof code == "string") code = splitLines(code); - makeChange(doc, {from: from, to: to, text: code, origin: origin}, null); - } - - // POSITION OBJECT - - function Pos(line, ch) { - if (!(this instanceof Pos)) return new Pos(line, ch); - this.line = line; this.ch = ch; - } - CodeMirror.Pos = Pos; - - function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} - function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} - function copyPos(x) {return Pos(x.line, x.ch);} - - // SELECTION - - function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} - function clipPos(doc, pos) { - if (pos.line < doc.first) return Pos(doc.first, 0); - var last = doc.first + doc.size - 1; - if (pos.line > last) return Pos(last, getLine(doc, last).text.length); - return clipToLen(pos, getLine(doc, pos.line).text.length); - } - function clipToLen(pos, linelen) { - var ch = pos.ch; - if (ch == null || ch > linelen) return Pos(pos.line, linelen); - else if (ch < 0) return Pos(pos.line, 0); - else return pos; - } - function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} - - // If shift is held, this will move the selection anchor. Otherwise, - // it'll set the whole selection. - function extendSelection(doc, pos, other, bias) { - if (doc.sel.shift || doc.sel.extend) { - var anchor = doc.sel.anchor; - if (other) { - var posBefore = posLess(pos, anchor); - if (posBefore != posLess(other, anchor)) { - anchor = pos; - pos = other; - } else if (posBefore != posLess(pos, other)) { - pos = other; - } - } - setSelection(doc, anchor, pos, bias); - } else { - setSelection(doc, pos, other || pos, bias); - } - if (doc.cm) doc.cm.curOp.userSelChange = true; - } - - function filterSelectionChange(doc, anchor, head) { - var obj = {anchor: anchor, head: head}; - signal(doc, "beforeSelectionChange", doc, obj); - if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); - obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head); - return obj; - } - - // Update the selection. Last two args are only used by - // updateDoc, since they have to be expressed in the line - // numbers before the update. - function setSelection(doc, anchor, head, bias, checkAtomic) { - if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) { - var filtered = filterSelectionChange(doc, anchor, head); - head = filtered.head; - anchor = filtered.anchor; - } - - var sel = doc.sel; - sel.goalColumn = null; - // Skip over atomic spans. - if (checkAtomic || !posEq(anchor, sel.anchor)) - anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push"); - if (checkAtomic || !posEq(head, sel.head)) - head = skipAtomic(doc, head, bias, checkAtomic != "push"); - - if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return; - - sel.anchor = anchor; sel.head = head; - var inv = posLess(head, anchor); - sel.from = inv ? head : anchor; - sel.to = inv ? anchor : head; - - if (doc.cm) - doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = - doc.cm.curOp.cursorActivity = true; - - signalLater(doc, "cursorActivity", doc); - } - - function reCheckSelection(cm) { - setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push"); - } - - function skipAtomic(doc, pos, bias, mayClear) { - var flipped = false, curPos = pos; - var dir = bias || 1; - doc.cantEdit = false; - search: for (;;) { - var line = getLine(doc, curPos.line); - if (line.markedSpans) { - for (var i = 0; i < line.markedSpans.length; ++i) { - var sp = line.markedSpans[i], m = sp.marker; - if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && - (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { - if (mayClear) { - signal(m, "beforeCursorEnter"); - if (m.explicitlyCleared) { - if (!line.markedSpans) break; - else {--i; continue;} - } - } - if (!m.atomic) continue; - var newPos = m.find()[dir < 0 ? "from" : "to"]; - if (posEq(newPos, curPos)) { - newPos.ch += dir; - if (newPos.ch < 0) { - if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); - else newPos = null; - } else if (newPos.ch > line.text.length) { - if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); - else newPos = null; - } - if (!newPos) { - if (flipped) { - // Driven in a corner -- no valid cursor position found at all - // -- try again *with* clearing, if we didn't already - if (!mayClear) return skipAtomic(doc, pos, bias, true); - // Otherwise, turn off editing until further notice, and return the start of the doc - doc.cantEdit = true; - return Pos(doc.first, 0); - } - flipped = true; newPos = pos; dir = -dir; - } - } - curPos = newPos; - continue search; - } - } - } - return curPos; - } - } - - // SCROLLING - - function scrollCursorIntoView(cm) { - var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin); - if (!cm.state.focused) return; - var display = cm.display, box = getRect(display.sizer), doScroll = null; - if (coords.top + box.top < 0) doScroll = true; - else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; - if (doScroll != null && !phantom) { - var hidden = display.cursor.style.display == "none"; - if (hidden) { - display.cursor.style.display = ""; - display.cursor.style.left = coords.left + "px"; - display.cursor.style.top = (coords.top - display.viewOffset) + "px"; - } - display.cursor.scrollIntoView(doScroll); - if (hidden) display.cursor.style.display = "none"; - } - } - - function scrollPosIntoView(cm, pos, margin) { - if (margin == null) margin = 0; - for (;;) { - var changed = false, coords = cursorCoords(cm, pos); - var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin); - var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; - if (scrollPos.scrollTop != null) { - setScrollTop(cm, scrollPos.scrollTop); - if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; - } - if (scrollPos.scrollLeft != null) { - setScrollLeft(cm, scrollPos.scrollLeft); - if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; - } - if (!changed) return coords; - } - } - - function scrollIntoView(cm, x1, y1, x2, y2) { - var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); - if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); - if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); - } - - function calculateScrollPos(cm, x1, y1, x2, y2) { - var display = cm.display, snapMargin = textHeight(cm.display); - if (y1 < 0) y1 = 0; - var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {}; - var docBottom = cm.doc.height + paddingVert(display); - var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; - if (y1 < screentop) { - result.scrollTop = atTop ? 0 : y1; - } else if (y2 > screentop + screen) { - var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); - if (newTop != screentop) result.scrollTop = newTop; - } - - var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft; - x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; - var gutterw = display.gutters.offsetWidth; - var atLeft = x1 < gutterw + 10; - if (x1 < screenleft + gutterw || atLeft) { - if (atLeft) x1 = 0; - result.scrollLeft = Math.max(0, x1 - 10 - gutterw); - } else if (x2 > screenw + screenleft - 3) { - result.scrollLeft = x2 + 10 - screenw; - } - return result; - } - - function updateScrollPos(cm, left, top) { - cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left, - scrollTop: top == null ? cm.doc.scrollTop : top}; - } - - function addToScrollPos(cm, left, top) { - var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop}); - var scroll = cm.display.scroller; - pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top)); - pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left)); - } - - // API UTILITIES - - function indentLine(cm, n, how, aggressive) { - var doc = cm.doc; - if (how == null) how = "add"; - if (how == "smart") { - if (!cm.doc.mode.indent) how = "prev"; - else var state = getStateBefore(cm, n); - } - - var tabSize = cm.options.tabSize; - var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); - var curSpaceString = line.text.match(/^\s*/)[0], indentation; - if (how == "smart") { - indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass) { - if (!aggressive) return; - how = "prev"; - } - } - if (how == "prev") { - if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); - else indentation = 0; - } else if (how == "add") { - indentation = curSpace + cm.options.indentUnit; - } else if (how == "subtract") { - indentation = curSpace - cm.options.indentUnit; - } else if (typeof how == "number") { - indentation = curSpace + how; - } - indentation = Math.max(0, indentation); - - var indentString = "", pos = 0; - if (cm.options.indentWithTabs) - for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} - if (pos < indentation) indentString += spaceStr(indentation - pos); - - if (indentString != curSpaceString) - replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); - line.stateAfter = null; - } - - function changeLine(cm, handle, op) { - var no = handle, line = handle, doc = cm.doc; - if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); - else no = lineNo(handle); - if (no == null) return null; - if (op(line, no)) regChange(cm, no, no + 1); - else return null; - return line; - } - - function findPosH(doc, pos, dir, unit, visually) { - var line = pos.line, ch = pos.ch, origDir = dir; - var lineObj = getLine(doc, line); - var possible = true; - function findNextLine() { - var l = line + dir; - if (l < doc.first || l >= doc.first + doc.size) return (possible = false); - line = l; - return lineObj = getLine(doc, l); - } - function moveOnce(boundToLine) { - var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); - if (next == null) { - if (!boundToLine && findNextLine()) { - if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); - else ch = dir < 0 ? lineObj.text.length : 0; - } else return (possible = false); - } else ch = next; - return true; - } - - if (unit == "char") moveOnce(); - else if (unit == "column") moveOnce(true); - else if (unit == "word" || unit == "group") { - var sawType = null, group = unit == "group"; - for (var first = true;; first = false) { - if (dir < 0 && !moveOnce(!first)) break; - var cur = lineObj.text.charAt(ch) || "\n"; - var type = isWordChar(cur) ? "w" - : !group ? null - : /\s/.test(cur) ? null - : "p"; - if (sawType && sawType != type) { - if (dir < 0) {dir = 1; moveOnce();} - break; - } - if (type) sawType = type; - if (dir > 0 && !moveOnce(!first)) break; - } - } - var result = skipAtomic(doc, Pos(line, ch), origDir, true); - if (!possible) result.hitSide = true; - return result; - } - - function findPosV(cm, pos, dir, unit) { - var doc = cm.doc, x = pos.left, y; - if (unit == "page") { - var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); - y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); - } else if (unit == "line") { - y = dir > 0 ? pos.bottom + 3 : pos.top - 3; - } - for (;;) { - var target = coordsChar(cm, x, y); - if (!target.outside) break; - if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } - y += dir * 5; - } - return target; - } - - function findWordAt(line, pos) { - var start = pos.ch, end = pos.ch; - if (line) { - if (pos.xRel < 0 || end == line.length) --start; else ++end; - var startChar = line.charAt(start); - var check = isWordChar(startChar) ? isWordChar - : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} - : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; - while (start > 0 && check(line.charAt(start - 1))) --start; - while (end < line.length && check(line.charAt(end))) ++end; - } - return {from: Pos(pos.line, start), to: Pos(pos.line, end)}; - } - - function selectLine(cm, line) { - extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0))); - } - - // PROTOTYPE - - // The publicly visible API. Note that operation(null, f) means - // 'wrap f in an operation, performed on its `this` parameter' - - CodeMirror.prototype = { - constructor: CodeMirror, - focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);}, - - setOption: function(option, value) { - var options = this.options, old = options[option]; - if (options[option] == value && option != "mode") return; - options[option] = value; - if (optionHandlers.hasOwnProperty(option)) - operation(this, optionHandlers[option])(this, value, old); - }, - - getOption: function(option) {return this.options[option];}, - getDoc: function() {return this.doc;}, - - addKeyMap: function(map, bottom) { - this.state.keyMaps[bottom ? "push" : "unshift"](map); - }, - removeKeyMap: function(map) { - var maps = this.state.keyMaps; - for (var i = 0; i < maps.length; ++i) - if ((typeof map == "string" ? maps[i].name : maps[i]) == map) { - maps.splice(i, 1); - return true; - } - }, - - addOverlay: operation(null, function(spec, options) { - var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); - if (mode.startState) throw new Error("Overlays may not be stateful."); - this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); - this.state.modeGen++; - regChange(this); - }), - removeOverlay: operation(null, function(spec) { - var overlays = this.state.overlays; - for (var i = 0; i < overlays.length; ++i) { - var cur = overlays[i].modeSpec; - if (cur == spec || typeof spec == "string" && cur.name == spec) { - overlays.splice(i, 1); - this.state.modeGen++; - regChange(this); - return; - } - } - }), - - indentLine: operation(null, function(n, dir, aggressive) { - if (typeof dir != "string" && typeof dir != "number") { - if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; - else dir = dir ? "add" : "subtract"; - } - if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); - }), - indentSelection: operation(null, function(how) { - var sel = this.doc.sel; - if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how); - var e = sel.to.line - (sel.to.ch ? 0 : 1); - for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how); - }), - - // Fetch the parser token for a given character. Useful for hacks - // that want to inspect the mode state (say, for completion). - getTokenAt: function(pos, precise) { - var doc = this.doc; - pos = clipPos(doc, pos); - var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode; - var line = getLine(doc, pos.line); - var stream = new StringStream(line.text, this.options.tabSize); - while (stream.pos < pos.ch && !stream.eol()) { - stream.start = stream.pos; - var style = mode.token(stream, state); - } - return {start: stream.start, - end: stream.pos, - string: stream.current(), - className: style || null, // Deprecated, use 'type' instead - type: style || null, - state: state}; - }, - - getTokenTypeAt: function(pos) { - pos = clipPos(this.doc, pos); - var styles = getLineStyles(this, getLine(this.doc, pos.line)); - var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; - for (;;) { - var mid = (before + after) >> 1; - if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; - else if (styles[mid * 2 + 1] < ch) before = mid + 1; - else return styles[mid * 2 + 2]; - } - }, - - getStateAfter: function(line, precise) { - var doc = this.doc; - line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); - return getStateBefore(this, line + 1, precise); - }, - - cursorCoords: function(start, mode) { - var pos, sel = this.doc.sel; - if (start == null) pos = sel.head; - else if (typeof start == "object") pos = clipPos(this.doc, start); - else pos = start ? sel.from : sel.to; - return cursorCoords(this, pos, mode || "page"); - }, - - charCoords: function(pos, mode) { - return charCoords(this, clipPos(this.doc, pos), mode || "page"); - }, - - coordsChar: function(coords, mode) { - coords = fromCoordSystem(this, coords, mode || "page"); - return coordsChar(this, coords.left, coords.top); - }, - - lineAtHeight: function(height, mode) { - height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; - return lineAtHeight(this.doc, height + this.display.viewOffset); - }, - heightAtLine: function(line, mode) { - var end = false, last = this.doc.first + this.doc.size - 1; - if (line < this.doc.first) line = this.doc.first; - else if (line > last) { line = last; end = true; } - var lineObj = getLine(this.doc, line); - return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top + - (end ? lineObj.height : 0); - }, - - defaultTextHeight: function() { return textHeight(this.display); }, - defaultCharWidth: function() { return charWidth(this.display); }, - - setGutterMarker: operation(null, function(line, gutterID, value) { - return changeLine(this, line, function(line) { - var markers = line.gutterMarkers || (line.gutterMarkers = {}); - markers[gutterID] = value; - if (!value && isEmpty(markers)) line.gutterMarkers = null; - return true; - }); - }), - - clearGutter: operation(null, function(gutterID) { - var cm = this, doc = cm.doc, i = doc.first; - doc.iter(function(line) { - if (line.gutterMarkers && line.gutterMarkers[gutterID]) { - line.gutterMarkers[gutterID] = null; - regChange(cm, i, i + 1); - if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; - } - ++i; - }); - }), - - addLineClass: operation(null, function(handle, where, cls) { - return changeLine(this, handle, function(line) { - var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; - if (!line[prop]) line[prop] = cls; - else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false; - else line[prop] += " " + cls; - return true; - }); - }), - - removeLineClass: operation(null, function(handle, where, cls) { - return changeLine(this, handle, function(line) { - var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; - var cur = line[prop]; - if (!cur) return false; - else if (cls == null) line[prop] = null; - else { - var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)")); - if (!found) return false; - var end = found.index + found[0].length; - line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; - } - return true; - }); - }), - - addLineWidget: operation(null, function(handle, node, options) { - return addLineWidget(this, handle, node, options); - }), - - removeLineWidget: function(widget) { widget.clear(); }, - - lineInfo: function(line) { - if (typeof line == "number") { - if (!isLine(this.doc, line)) return null; - var n = line; - line = getLine(this.doc, line); - if (!line) return null; - } else { - var n = lineNo(line); - if (n == null) return null; - } - return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, - textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, - widgets: line.widgets}; - }, - - getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};}, - - addWidget: function(pos, node, scroll, vert, horiz) { - var display = this.display; - pos = cursorCoords(this, clipPos(this.doc, pos)); - var top = pos.bottom, left = pos.left; - node.style.position = "absolute"; - display.sizer.appendChild(node); - if (vert == "over") { - top = pos.top; - } else if (vert == "above" || vert == "near") { - var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), - hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); - // Default to positioning above (if specified and possible); otherwise default to positioning below - if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) - top = pos.top - node.offsetHeight; - else if (pos.bottom + node.offsetHeight <= vspace) - top = pos.bottom; - if (left + node.offsetWidth > hspace) - left = hspace - node.offsetWidth; - } - node.style.top = top + "px"; - node.style.left = node.style.right = ""; - if (horiz == "right") { - left = display.sizer.clientWidth - node.offsetWidth; - node.style.right = "0px"; - } else { - if (horiz == "left") left = 0; - else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; - node.style.left = left + "px"; - } - if (scroll) - scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); - }, - - triggerOnKeyDown: operation(null, onKeyDown), - - execCommand: function(cmd) {return commands[cmd](this);}, - - findPosH: function(from, amount, unit, visually) { - var dir = 1; - if (amount < 0) { dir = -1; amount = -amount; } - for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { - cur = findPosH(this.doc, cur, dir, unit, visually); - if (cur.hitSide) break; - } - return cur; - }, - - moveH: operation(null, function(dir, unit) { - var sel = this.doc.sel, pos; - if (sel.shift || sel.extend || posEq(sel.from, sel.to)) - pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually); - else - pos = dir < 0 ? sel.from : sel.to; - extendSelection(this.doc, pos, pos, dir); - }), - - deleteH: operation(null, function(dir, unit) { - var sel = this.doc.sel; - if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete"); - else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete"); - this.curOp.userSelChange = true; - }), - - findPosV: function(from, amount, unit, goalColumn) { - var dir = 1, x = goalColumn; - if (amount < 0) { dir = -1; amount = -amount; } - for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { - var coords = cursorCoords(this, cur, "div"); - if (x == null) x = coords.left; - else coords.left = x; - cur = findPosV(this, coords, dir, unit); - if (cur.hitSide) break; - } - return cur; - }, - - moveV: operation(null, function(dir, unit) { - var sel = this.doc.sel; - var pos = cursorCoords(this, sel.head, "div"); - if (sel.goalColumn != null) pos.left = sel.goalColumn; - var target = findPosV(this, pos, dir, unit); - - if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top); - extendSelection(this.doc, target, target, dir); - sel.goalColumn = pos.left; - }), - - toggleOverwrite: function(value) { - if (value != null && value == this.state.overwrite) return; - if (this.state.overwrite = !this.state.overwrite) - this.display.cursor.className += " CodeMirror-overwrite"; - else - this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", ""); - }, - hasFocus: function() { return this.state.focused; }, - - scrollTo: operation(null, function(x, y) { - updateScrollPos(this, x, y); - }), - getScrollInfo: function() { - var scroller = this.display.scroller, co = scrollerCutOff; - return {left: scroller.scrollLeft, top: scroller.scrollTop, - height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, - clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; - }, - - scrollIntoView: operation(null, function(pos, margin) { - if (typeof pos == "number") pos = Pos(pos, 0); - if (!margin) margin = 0; - var coords = pos; - - if (!pos || pos.line != null) { - this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head; - this.curOp.scrollToPosMargin = margin; - coords = cursorCoords(this, this.curOp.scrollToPos); - } - var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin); - updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop); - }), - - setSize: function(width, height) { - function interpret(val) { - return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; - } - if (width != null) this.display.wrapper.style.width = interpret(width); - if (height != null) this.display.wrapper.style.height = interpret(height); - this.refresh(); - }, - - on: function(type, f) {on(this, type, f);}, - off: function(type, f) {off(this, type, f);}, - - operation: function(f){return runInOp(this, f);}, - - refresh: operation(null, function() { - clearCaches(this); - updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop); - regChange(this); - }), - - swapDoc: operation(null, function(doc) { - var old = this.doc; - old.cm = null; - attachDoc(this, doc); - clearCaches(this); - resetInput(this, true); - updateScrollPos(this, doc.scrollLeft, doc.scrollTop); - return old; - }), - - getInputField: function(){return this.display.input;}, - getWrapperElement: function(){return this.display.wrapper;}, - getScrollerElement: function(){return this.display.scroller;}, - getGutterElement: function(){return this.display.gutters;} - }; - - // OPTION DEFAULTS - - var optionHandlers = CodeMirror.optionHandlers = {}; - - // The default configuration options. - var defaults = CodeMirror.defaults = {}; - - function option(name, deflt, handle, notOnInit) { - CodeMirror.defaults[name] = deflt; - if (handle) optionHandlers[name] = - notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; - } - - var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; - - // These two are, on init, called from the constructor because they - // have to be initialized before the editor can start at all. - option("value", "", function(cm, val) { - cm.setValue(val); - }, true); - option("mode", null, function(cm, val) { - cm.doc.modeOption = val; - loadMode(cm); - }, true); - - option("indentUnit", 2, loadMode, true); - option("indentWithTabs", false); - option("smartIndent", true); - option("tabSize", 4, function(cm) { - loadMode(cm); - clearCaches(cm); - regChange(cm); - }, true); - option("electricChars", true); - option("rtlMoveVisually", !windows); - - option("theme", "default", function(cm) { - themeChanged(cm); - guttersChanged(cm); - }, true); - option("keyMap", "default", keyMapChanged); - option("extraKeys", null); - - option("onKeyEvent", null); - option("onDragEvent", null); - - option("lineWrapping", false, wrappingChanged, true); - option("gutters", [], function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("fixedGutter", true, function(cm, val) { - cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; - cm.refresh(); - }, true); - option("coverGutterNextToScrollbar", false, updateScrollbars, true); - option("lineNumbers", false, function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("firstLineNumber", 1, guttersChanged, true); - option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); - option("showCursorWhenSelecting", false, updateSelection, true); - - option("readOnly", false, function(cm, val) { - if (val == "nocursor") {onBlur(cm); cm.display.input.blur();} - else if (!val) resetInput(cm, true); - }); - option("dragDrop", true); - - option("cursorBlinkRate", 530); - option("cursorScrollMargin", 0); - option("cursorHeight", 1); - option("workTime", 100); - option("workDelay", 100); - option("flattenSpans", true); - option("pollInterval", 100); - option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;}); - option("historyEventDelay", 500); - option("viewportMargin", 10, function(cm){cm.refresh();}, true); - option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true); - option("moveInputWithCursor", true, function(cm, val) { - if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; - }); - - option("tabindex", null, function(cm, val) { - cm.display.input.tabIndex = val || ""; - }); - option("autofocus", null); - - // MODE DEFINITION AND QUERYING - - // Known modes, by name and by MIME - var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; - - CodeMirror.defineMode = function(name, mode) { - if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; - if (arguments.length > 2) { - mode.dependencies = []; - for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); - } - modes[name] = mode; - }; - - CodeMirror.defineMIME = function(mime, spec) { - mimeModes[mime] = spec; - }; - - CodeMirror.resolveMode = function(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { - spec = mimeModes[spec]; - } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { - var found = mimeModes[spec.name]; - spec = createObj(found, spec); - spec.name = found.name; - } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { - return CodeMirror.resolveMode("application/xml"); - } - if (typeof spec == "string") return {name: spec}; - else return spec || {name: "null"}; - }; - - CodeMirror.getMode = function(options, spec) { - spec = CodeMirror.resolveMode(spec); - var mfactory = modes[spec.name]; - if (!mfactory) return CodeMirror.getMode(options, "text/plain"); - var modeObj = mfactory(options, spec); - if (modeExtensions.hasOwnProperty(spec.name)) { - var exts = modeExtensions[spec.name]; - for (var prop in exts) { - if (!exts.hasOwnProperty(prop)) continue; - if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; - modeObj[prop] = exts[prop]; - } - } - modeObj.name = spec.name; - return modeObj; - }; - - CodeMirror.defineMode("null", function() { - return {token: function(stream) {stream.skipToEnd();}}; - }); - CodeMirror.defineMIME("text/plain", "null"); - - var modeExtensions = CodeMirror.modeExtensions = {}; - CodeMirror.extendMode = function(mode, properties) { - var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); - copyObj(properties, exts); - }; - - // EXTENSIONS - - CodeMirror.defineExtension = function(name, func) { - CodeMirror.prototype[name] = func; - }; - CodeMirror.defineDocExtension = function(name, func) { - Doc.prototype[name] = func; - }; - CodeMirror.defineOption = option; - - var initHooks = []; - CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; - - // MODE STATE HANDLING - - // Utility functions for working with state. Exported because modes - // sometimes need to do this. - function copyState(mode, state) { - if (state === true) return state; - if (mode.copyState) return mode.copyState(state); - var nstate = {}; - for (var n in state) { - var val = state[n]; - if (val instanceof Array) val = val.concat([]); - nstate[n] = val; - } - return nstate; - } - CodeMirror.copyState = copyState; - - function startState(mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; - } - CodeMirror.startState = startState; - - CodeMirror.innerMode = function(mode, state) { - while (mode.innerMode) { - var info = mode.innerMode(state); - state = info.state; - mode = info.mode; - } - return info || {mode: mode, state: state}; - }; - - // STANDARD COMMANDS - - var commands = CodeMirror.commands = { - selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));}, - killLine: function(cm) { - var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); - if (!sel && cm.getLine(from.line).length == from.ch) - cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete"); - else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete"); - }, - deleteLine: function(cm) { - var l = cm.getCursor().line; - cm.replaceRange("", Pos(l, 0), Pos(l), "+delete"); - }, - delLineLeft: function(cm) { - var cur = cm.getCursor(); - cm.replaceRange("", Pos(cur.line, 0), cur, "+delete"); - }, - undo: function(cm) {cm.undo();}, - redo: function(cm) {cm.redo();}, - goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, - goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, - goLineStart: function(cm) { - cm.extendSelection(lineStart(cm, cm.getCursor().line)); - }, - goLineStartSmart: function(cm) { - var cur = cm.getCursor(), start = lineStart(cm, cur.line); - var line = cm.getLineHandle(start.line); - var order = getOrder(line); - if (!order || order[0].level == 0) { - var firstNonWS = Math.max(0, line.text.search(/\S/)); - var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch; - cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS)); - } else cm.extendSelection(start); - }, - goLineEnd: function(cm) { - cm.extendSelection(lineEnd(cm, cm.getCursor().line)); - }, - goLineRight: function(cm) { - var top = cm.charCoords(cm.getCursor(), "div").top + 5; - cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")); - }, - goLineLeft: function(cm) { - var top = cm.charCoords(cm.getCursor(), "div").top + 5; - cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div")); - }, - goLineUp: function(cm) {cm.moveV(-1, "line");}, - goLineDown: function(cm) {cm.moveV(1, "line");}, - goPageUp: function(cm) {cm.moveV(-1, "page");}, - goPageDown: function(cm) {cm.moveV(1, "page");}, - goCharLeft: function(cm) {cm.moveH(-1, "char");}, - goCharRight: function(cm) {cm.moveH(1, "char");}, - goColumnLeft: function(cm) {cm.moveH(-1, "column");}, - goColumnRight: function(cm) {cm.moveH(1, "column");}, - goWordLeft: function(cm) {cm.moveH(-1, "word");}, - goGroupRight: function(cm) {cm.moveH(1, "group");}, - goGroupLeft: function(cm) {cm.moveH(-1, "group");}, - goWordRight: function(cm) {cm.moveH(1, "word");}, - delCharBefore: function(cm) {cm.deleteH(-1, "char");}, - delCharAfter: function(cm) {cm.deleteH(1, "char");}, - delWordBefore: function(cm) {cm.deleteH(-1, "word");}, - delWordAfter: function(cm) {cm.deleteH(1, "word");}, - delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, - delGroupAfter: function(cm) {cm.deleteH(1, "group");}, - indentAuto: function(cm) {cm.indentSelection("smart");}, - indentMore: function(cm) {cm.indentSelection("add");}, - indentLess: function(cm) {cm.indentSelection("subtract");}, - insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");}, - defaultTab: function(cm) { - if (cm.somethingSelected()) cm.indentSelection("add"); - else cm.replaceSelection("\t", "end", "+input"); - }, - transposeChars: function(cm) { - var cur = cm.getCursor(), line = cm.getLine(cur.line); - if (cur.ch > 0 && cur.ch < line.length - 1) - cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), - Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1)); - }, - newlineAndIndent: function(cm) { - operation(cm, function() { - cm.replaceSelection("\n", "end", "+input"); - cm.indentLine(cm.getCursor().line, null, true); - })(); - }, - toggleOverwrite: function(cm) {cm.toggleOverwrite();} - }; - - // STANDARD KEYMAPS - - var keyMap = CodeMirror.keyMap = {}; - keyMap.basic = { - "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", - "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", - "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" - }; - // Note that the save and find-related commands aren't defined by - // default. Unknown commands are simply ignored. - keyMap.pcDefault = { - "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", - "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", - "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", - "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", - "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", - "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", - fallthrough: "basic" - }; - keyMap.macDefault = { - "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", - "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", - "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", - "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", - "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft", - fallthrough: ["basic", "emacsy"] - }; - keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; - keyMap.emacsy = { - "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", - "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", - "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", - "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" - }; - - // KEYMAP DISPATCH - - function getKeyMap(val) { - if (typeof val == "string") return keyMap[val]; - else return val; - } - - function lookupKey(name, maps, handle) { - function lookup(map) { - map = getKeyMap(map); - var found = map[name]; - if (found === false) return "stop"; - if (found != null && handle(found)) return true; - if (map.nofallthrough) return "stop"; - - var fallthrough = map.fallthrough; - if (fallthrough == null) return false; - if (Object.prototype.toString.call(fallthrough) != "[object Array]") - return lookup(fallthrough); - for (var i = 0, e = fallthrough.length; i < e; ++i) { - var done = lookup(fallthrough[i]); - if (done) return done; - } - return false; - } - - for (var i = 0; i < maps.length; ++i) { - var done = lookup(maps[i]); - if (done) return done != "stop"; - } - } - function isModifierKey(event) { - var name = keyNames[event.keyCode]; - return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; - } - function keyName(event, noShift) { - if (opera && event.keyCode == 34 && event["char"]) return false; - var name = keyNames[event.keyCode]; - if (name == null || event.altGraphKey) return false; - if (event.altKey) name = "Alt-" + name; - if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name; - if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name; - if (!noShift && event.shiftKey) name = "Shift-" + name; - return name; - } - CodeMirror.lookupKey = lookupKey; - CodeMirror.isModifierKey = isModifierKey; - CodeMirror.keyName = keyName; - - // FROMTEXTAREA - - CodeMirror.fromTextArea = function(textarea, options) { - if (!options) options = {}; - options.value = textarea.value; - if (!options.tabindex && textarea.tabindex) - options.tabindex = textarea.tabindex; - if (!options.placeholder && textarea.placeholder) - options.placeholder = textarea.placeholder; - // Set autofocus to true if this textarea is focused, or if it has - // autofocus and no other element is focused. - if (options.autofocus == null) { - var hasFocus = document.body; - // doc.activeElement occasionally throws on IE - try { hasFocus = document.activeElement; } catch(e) {} - options.autofocus = hasFocus == textarea || - textarea.getAttribute("autofocus") != null && hasFocus == document.body; - } - - function save() {textarea.value = cm.getValue();} - if (textarea.form) { - on(textarea.form, "submit", save); - // Deplorable hack to make the submit method do the right thing. - if (!options.leaveSubmitMethodAlone) { - var form = textarea.form, realSubmit = form.submit; - try { - var wrappedSubmit = form.submit = function() { - save(); - form.submit = realSubmit; - form.submit(); - form.submit = wrappedSubmit; - }; - } catch(e) {} - } - } - - textarea.style.display = "none"; - var cm = CodeMirror(function(node) { - textarea.parentNode.insertBefore(node, textarea.nextSibling); - }, options); - cm.save = save; - cm.getTextArea = function() { return textarea; }; - cm.toTextArea = function() { - save(); - textarea.parentNode.removeChild(cm.getWrapperElement()); - textarea.style.display = ""; - if (textarea.form) { - off(textarea.form, "submit", save); - if (typeof textarea.form.submit == "function") - textarea.form.submit = realSubmit; - } - }; - return cm; - }; - - // STRING STREAM - - // Fed to the mode parsers, provides helper functions to make - // parsers more succinct. - - // The character stream used by a mode's parser. - function StringStream(string, tabSize) { - this.pos = this.start = 0; - this.string = string; - this.tabSize = tabSize || 8; - this.lastColumnPos = this.lastColumnValue = 0; - } - - StringStream.prototype = { - eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == 0;}, - peek: function() {return this.string.charAt(this.pos) || undefined;}, - next: function() { - if (this.pos < this.string.length) - return this.string.charAt(this.pos++); - }, - eat: function(match) { - var ch = this.string.charAt(this.pos); - if (typeof match == "string") var ok = ch == match; - else var ok = ch && (match.test ? match.test(ch) : match(ch)); - if (ok) {++this.pos; return ch;} - }, - eatWhile: function(match) { - var start = this.pos; - while (this.eat(match)){} - return this.pos > start; - }, - eatSpace: function() { - var start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; - return this.pos > start; - }, - skipToEnd: function() {this.pos = this.string.length;}, - skipTo: function(ch) { - var found = this.string.indexOf(ch, this.pos); - if (found > -1) {this.pos = found; return true;} - }, - backUp: function(n) {this.pos -= n;}, - column: function() { - if (this.lastColumnPos < this.start) { - this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); - this.lastColumnPos = this.start; - } - return this.lastColumnValue; - }, - indentation: function() {return countColumn(this.string, null, this.tabSize);}, - match: function(pattern, consume, caseInsensitive) { - if (typeof pattern == "string") { - var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - var substr = this.string.substr(this.pos, pattern.length); - if (cased(substr) == cased(pattern)) { - if (consume !== false) this.pos += pattern.length; - return true; - } - } else { - var match = this.string.slice(this.pos).match(pattern); - if (match && match.index > 0) return null; - if (match && consume !== false) this.pos += match[0].length; - return match; - } - }, - current: function(){return this.string.slice(this.start, this.pos);} - }; - CodeMirror.StringStream = StringStream; - - // TEXTMARKERS - - function TextMarker(doc, type) { - this.lines = []; - this.type = type; - this.doc = doc; - } - CodeMirror.TextMarker = TextMarker; - - TextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return; - var cm = this.doc.cm, withOp = cm && !cm.curOp; - if (withOp) startOperation(cm); - var min = null, max = null; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (span.to != null) max = lineNo(line); - line.markedSpans = removeMarkedSpan(line.markedSpans, span); - if (span.from != null) - min = lineNo(line); - else if (this.collapsed && !lineIsHidden(this.doc, line) && cm) - updateLineHeight(line, textHeight(cm.display)); - } - if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { - var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual); - if (len > cm.display.maxLineLength) { - cm.display.maxLine = visual; - cm.display.maxLineLength = len; - cm.display.maxLineChanged = true; - } - } - - if (min != null && cm) regChange(cm, min, max + 1); - this.lines.length = 0; - this.explicitlyCleared = true; - if (this.atomic && this.doc.cantEdit) { - this.doc.cantEdit = false; - if (cm) reCheckSelection(cm); - } - if (withOp) endOperation(cm); - signalLater(this, "clear"); - }; - - TextMarker.prototype.find = function() { - var from, to; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (span.from != null || span.to != null) { - var found = lineNo(line); - if (span.from != null) from = Pos(found, span.from); - if (span.to != null) to = Pos(found, span.to); - } - } - if (this.type == "bookmark") return from; - return from && {from: from, to: to}; - }; - - TextMarker.prototype.changed = function() { - var pos = this.find(), cm = this.doc.cm; - if (!pos || !cm) return; - var line = getLine(this.doc, pos.from.line); - clearCachedMeasurement(cm, line); - if (pos.from.line >= cm.display.showingFrom && pos.from.line < cm.display.showingTo) { - for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) { - if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight); - break; - } - runInOp(cm, function() { cm.curOp.selectionChanged = true; }); - } - }; - - TextMarker.prototype.attachLine = function(line) { - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) - (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); - } - this.lines.push(line); - }; - TextMarker.prototype.detachLine = function(line) { - this.lines.splice(indexOf(this.lines, line), 1); - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); - } - }; - - function markText(doc, from, to, options, type) { - if (options && options.shared) return markTextShared(doc, from, to, options, type); - if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); - - var marker = new TextMarker(doc, type); - if (type == "range" && !posLess(from, to)) return marker; - if (options) copyObj(options, marker); - if (marker.replacedWith) { - marker.collapsed = true; - marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); - if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true; - } - if (marker.collapsed) sawCollapsedSpans = true; - - if (marker.addToHistory) - addToHistory(doc, {from: from, to: to, origin: "markText"}, - {head: doc.sel.head, anchor: doc.sel.anchor}, NaN); - - var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine; - doc.iter(curLine, to.line + 1, function(line) { - if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine) - updateMaxLine = true; - var span = {from: null, to: null, marker: marker}; - size += line.text.length; - if (curLine == from.line) {span.from = from.ch; size -= from.ch;} - if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;} - if (marker.collapsed) { - if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch); - if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch); - else updateLineHeight(line, 0); - } - addMarkedSpan(line, span); - ++curLine; - }); - if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { - if (lineIsHidden(doc, line)) updateLineHeight(line, 0); - }); - - if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); - - if (marker.readOnly) { - sawReadOnlySpans = true; - if (doc.history.done.length || doc.history.undone.length) - doc.clearHistory(); - } - if (marker.collapsed) { - if (collapsedAtStart != collapsedAtEnd) - throw new Error("Inserting collapsed marker overlapping an existing one"); - marker.size = size; - marker.atomic = true; - } - if (cm) { - if (updateMaxLine) cm.curOp.updateMaxLine = true; - if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed) - regChange(cm, from.line, to.line + 1); - if (marker.atomic) reCheckSelection(cm); - } - return marker; - } - - // SHARED TEXTMARKERS - - function SharedTextMarker(markers, primary) { - this.markers = markers; - this.primary = primary; - for (var i = 0, me = this; i < markers.length; ++i) { - markers[i].parent = this; - on(markers[i], "clear", function(){me.clear();}); - } - } - CodeMirror.SharedTextMarker = SharedTextMarker; - - SharedTextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return; - this.explicitlyCleared = true; - for (var i = 0; i < this.markers.length; ++i) - this.markers[i].clear(); - signalLater(this, "clear"); - }; - SharedTextMarker.prototype.find = function() { - return this.primary.find(); - }; - - function markTextShared(doc, from, to, options, type) { - options = copyObj(options); - options.shared = false; - var markers = [markText(doc, from, to, options, type)], primary = markers[0]; - var widget = options.replacedWith; - linkedDocs(doc, function(doc) { - if (widget) options.replacedWith = widget.cloneNode(true); - markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); - for (var i = 0; i < doc.linked.length; ++i) - if (doc.linked[i].isParent) return; - primary = lst(markers); - }); - return new SharedTextMarker(markers, primary); - } - - // TEXTMARKER SPANS - - function getMarkedSpanFor(spans, marker) { - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.marker == marker) return span; - } - } - function removeMarkedSpan(spans, span) { - for (var r, i = 0; i < spans.length; ++i) - if (spans[i] != span) (r || (r = [])).push(spans[i]); - return r; - } - function addMarkedSpan(line, span) { - line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; - span.marker.attachLine(line); - } - - function markedSpansBefore(old, startCh, isInsert) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) { - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); - (nw || (nw = [])).push({from: span.from, - to: endsAfter ? null : span.to, - marker: marker}); - } - } - return nw; - } - - function markedSpansAfter(old, endCh, isInsert) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); - if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) { - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); - (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, - to: span.to == null ? null : span.to - endCh, - marker: marker}); - } - } - return nw; - } - - function stretchSpansOverChange(doc, change) { - var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; - var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; - if (!oldFirst && !oldLast) return null; - - var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to); - // Get the spans that 'stick out' on both sides - var first = markedSpansBefore(oldFirst, startCh, isInsert); - var last = markedSpansAfter(oldLast, endCh, isInsert); - - // Next, merge those two ends - var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); - if (first) { - // Fix up .to properties of first - for (var i = 0; i < first.length; ++i) { - var span = first[i]; - if (span.to == null) { - var found = getMarkedSpanFor(last, span.marker); - if (!found) span.to = startCh; - else if (sameLine) span.to = found.to == null ? null : found.to + offset; - } - } - } - if (last) { - // Fix up .from in last (or move them into first in case of sameLine) - for (var i = 0; i < last.length; ++i) { - var span = last[i]; - if (span.to != null) span.to += offset; - if (span.from == null) { - var found = getMarkedSpanFor(first, span.marker); - if (!found) { - span.from = offset; - if (sameLine) (first || (first = [])).push(span); - } - } else { - span.from += offset; - if (sameLine) (first || (first = [])).push(span); - } - } - } - if (sameLine && first) { - // Make sure we didn't create any zero-length spans - for (var i = 0; i < first.length; ++i) - if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark") - first.splice(i--, 1); - if (!first.length) first = null; - } - - var newMarkers = [first]; - if (!sameLine) { - // Fill gap with whole-line-spans - var gap = change.text.length - 2, gapMarkers; - if (gap > 0 && first) - for (var i = 0; i < first.length; ++i) - if (first[i].to == null) - (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); - for (var i = 0; i < gap; ++i) - newMarkers.push(gapMarkers); - newMarkers.push(last); - } - return newMarkers; - } - - function mergeOldSpans(doc, change) { - var old = getOldSpans(doc, change); - var stretched = stretchSpansOverChange(doc, change); - if (!old) return stretched; - if (!stretched) return old; - - for (var i = 0; i < old.length; ++i) { - var oldCur = old[i], stretchCur = stretched[i]; - if (oldCur && stretchCur) { - spans: for (var j = 0; j < stretchCur.length; ++j) { - var span = stretchCur[j]; - for (var k = 0; k < oldCur.length; ++k) - if (oldCur[k].marker == span.marker) continue spans; - oldCur.push(span); - } - } else if (stretchCur) { - old[i] = stretchCur; - } - } - return old; - } - - function removeReadOnlyRanges(doc, from, to) { - var markers = null; - doc.iter(from.line, to.line + 1, function(line) { - if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { - var mark = line.markedSpans[i].marker; - if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) - (markers || (markers = [])).push(mark); - } - }); - if (!markers) return null; - var parts = [{from: from, to: to}]; - for (var i = 0; i < markers.length; ++i) { - var mk = markers[i], m = mk.find(); - for (var j = 0; j < parts.length; ++j) { - var p = parts[j]; - if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue; - var newParts = [j, 1]; - if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from)) - newParts.push({from: p.from, to: m.from}); - if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to)) - newParts.push({from: m.to, to: p.to}); - parts.splice.apply(parts, newParts); - j += newParts.length - 1; - } - } - return parts; - } - - function collapsedSpanAt(line, ch) { - var sps = sawCollapsedSpans && line.markedSpans, found; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (!sp.marker.collapsed) continue; - if ((sp.from == null || sp.from < ch) && - (sp.to == null || sp.to > ch) && - (!found || found.width < sp.marker.width)) - found = sp.marker; - } - return found; - } - function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); } - function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); } - - function visualLine(doc, line) { - var merged; - while (merged = collapsedSpanAtStart(line)) - line = getLine(doc, merged.find().from.line); - return line; - } - - function lineIsHidden(doc, line) { - var sps = sawCollapsedSpans && line.markedSpans; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (!sp.marker.collapsed) continue; - if (sp.from == null) return true; - if (sp.marker.replacedWith) continue; - if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) - return true; - } - } - function lineIsHiddenInner(doc, line, span) { - if (span.to == null) { - var end = span.marker.find().to, endLine = getLine(doc, end.line); - return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker)); - } - if (span.marker.inclusiveRight && span.to == line.text.length) - return true; - for (var sp, i = 0; i < line.markedSpans.length; ++i) { - sp = line.markedSpans[i]; - if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to && - (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && - lineIsHiddenInner(doc, line, sp)) return true; - } - } - - function detachMarkedSpans(line) { - var spans = line.markedSpans; - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - spans[i].marker.detachLine(line); - line.markedSpans = null; - } - - function attachMarkedSpans(line, spans) { - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - spans[i].marker.attachLine(line); - line.markedSpans = spans; - } - - // LINE WIDGETS - - var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { - for (var opt in options) if (options.hasOwnProperty(opt)) - this[opt] = options[opt]; - this.cm = cm; - this.node = node; - }; - function widgetOperation(f) { - return function() { - var withOp = !this.cm.curOp; - if (withOp) startOperation(this.cm); - try {var result = f.apply(this, arguments);} - finally {if (withOp) endOperation(this.cm);} - return result; - }; - } - LineWidget.prototype.clear = widgetOperation(function() { - var ws = this.line.widgets, no = lineNo(this.line); - if (no == null || !ws) return; - for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); - if (!ws.length) this.line.widgets = null; - updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this))); - regChange(this.cm, no, no + 1); - }); - LineWidget.prototype.changed = widgetOperation(function() { - var oldH = this.height; - this.height = null; - var diff = widgetHeight(this) - oldH; - if (!diff) return; - updateLineHeight(this.line, this.line.height + diff); - var no = lineNo(this.line); - regChange(this.cm, no, no + 1); - }); - - function widgetHeight(widget) { - if (widget.height != null) return widget.height; - if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1) - removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative")); - return widget.height = widget.node.offsetHeight; - } - - function addLineWidget(cm, handle, node, options) { - var widget = new LineWidget(cm, node, options); - if (widget.noHScroll) cm.display.alignWidgets = true; - changeLine(cm, handle, function(line) { - (line.widgets || (line.widgets = [])).push(widget); - widget.line = line; - if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) { - var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop; - updateLineHeight(line, line.height + widgetHeight(widget)); - if (aboveVisible) addToScrollPos(cm, 0, widget.height); - } - return true; - }); - return widget; - } - - // LINE DATA STRUCTURE - - // Line objects. These hold state related to a line, including - // highlighting info (the styles array). - function makeLine(text, markedSpans, estimateHeight) { - var line = {text: text}; - attachMarkedSpans(line, markedSpans); - line.height = estimateHeight ? estimateHeight(line) : 1; - return line; - } - - function updateLine(line, text, markedSpans, estimateHeight) { - line.text = text; - if (line.stateAfter) line.stateAfter = null; - if (line.styles) line.styles = null; - if (line.order != null) line.order = null; - detachMarkedSpans(line); - attachMarkedSpans(line, markedSpans); - var estHeight = estimateHeight ? estimateHeight(line) : 1; - if (estHeight != line.height) updateLineHeight(line, estHeight); - } - - function cleanUpLine(line) { - line.parent = null; - detachMarkedSpans(line); - } - - // Run the given mode's parser over a line, update the styles - // array, which contains alternating fragments of text and CSS - // classes. - function runMode(cm, text, mode, state, f) { - var flattenSpans = mode.flattenSpans; - if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; - var curStart = 0, curStyle = null; - var stream = new StringStream(text, cm.options.tabSize), style; - if (text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol()) { - if (stream.pos > cm.options.maxHighlightLength) { - flattenSpans = false; - // Webkit seems to refuse to render text nodes longer than 57444 characters - stream.pos = Math.min(text.length, stream.start + 50000); - style = null; - } else { - style = mode.token(stream, state); - } - if (!flattenSpans || curStyle != style) { - if (curStart < stream.start) f(stream.start, curStyle); - curStart = stream.start; curStyle = style; - } - stream.start = stream.pos; - } - if (curStart < stream.pos) f(stream.pos, curStyle); - } - - function highlightLine(cm, line, state) { - // A styles array always starts with a number identifying the - // mode/overlays that it is based on (for easy invalidation). - var st = [cm.state.modeGen]; - // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);}); - - // Run overlays, adjust style array. - for (var o = 0; o < cm.state.overlays.length; ++o) { - var overlay = cm.state.overlays[o], i = 1, at = 0; - runMode(cm, line.text, overlay.mode, true, function(end, style) { - var start = i; - // Ensure there's a token end at the current position, and that i points at it - while (at < end) { - var i_end = st[i]; - if (i_end > end) - st.splice(i, 1, end, st[i+1], i_end); - i += 2; - at = Math.min(end, i_end); - } - if (!style) return; - if (overlay.opaque) { - st.splice(start, i - start, end, style); - i = start + 2; - } else { - for (; start < i; start += 2) { - var cur = st[start+1]; - st[start+1] = cur ? cur + " " + style : style; - } - } - }); - } - - return st; - } - - function getLineStyles(cm, line) { - if (!line.styles || line.styles[0] != cm.state.modeGen) - line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); - return line.styles; - } - - // Lightweight form of highlight -- proceed over this line and - // update state, but don't save a style array. - function processLine(cm, line, state) { - var mode = cm.doc.mode; - var stream = new StringStream(line.text, cm.options.tabSize); - if (line.text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { - mode.token(stream, state); - stream.start = stream.pos; - } - } - - var styleToClassCache = {}; - function styleToClass(style) { - if (!style) return null; - return styleToClassCache[style] || - (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); - } - - function lineContent(cm, realLine, measure) { - var merged, line = realLine, empty = true; - while (merged = collapsedSpanAtStart(line)) - line = getLine(cm.doc, merged.find().from.line); - - var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure, - measure: null, measuredSomething: false, cm: cm}; - if (line.textClass) builder.pre.className = line.textClass; - - do { - if (line.text) empty = false; - builder.measure = line == realLine && measure; - builder.pos = 0; - builder.addToken = builder.measure ? buildTokenMeasure : buildToken; - if ((ie || webkit) && cm.getOption("lineWrapping")) - builder.addToken = buildTokenSplitSpaces(builder.addToken); - var next = insertLineContent(line, builder, getLineStyles(cm, line)); - if (measure && line == realLine && !builder.measuredSomething) { - measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); - builder.measuredSomething = true; - } - if (next) line = getLine(cm.doc, next.to.line); - } while (next); - - if (measure && !builder.measuredSomething && !measure[0]) - measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); - if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine)) - builder.pre.appendChild(document.createTextNode("\u00a0")); - - var order; - // Work around problem with the reported dimensions of single-char - // direction spans on IE (issue #1129). See also the comment in - // cursorCoords. - if (measure && ie && (order = getOrder(line))) { - var l = order.length - 1; - if (order[l].from == order[l].to) --l; - var last = order[l], prev = order[l - 1]; - if (last.from + 1 == last.to && prev && last.level < prev.level) { - var span = measure[builder.pos - 1]; - if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure), - span.nextSibling); - } - } - - signal(cm, "renderLine", cm, realLine, builder.pre); - return builder.pre; - } - - var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g; - function buildToken(builder, text, style, startStyle, endStyle) { - if (!text) return; - if (!tokenSpecialChars.test(text)) { - builder.col += text.length; - var content = document.createTextNode(text); - } else { - var content = document.createDocumentFragment(), pos = 0; - while (true) { - tokenSpecialChars.lastIndex = pos; - var m = tokenSpecialChars.exec(text); - var skipped = m ? m.index - pos : text.length - pos; - if (skipped) { - content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); - builder.col += skipped; - } - if (!m) break; - pos += skipped + 1; - if (m[0] == "\t") { - var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; - content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); - builder.col += tabWidth; - } else { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + m[0].charCodeAt(0).toString(16); - content.appendChild(token); - builder.col += 1; - } - } - } - if (style || startStyle || endStyle || builder.measure) { - var fullStyle = style || ""; - if (startStyle) fullStyle += startStyle; - if (endStyle) fullStyle += endStyle; - return builder.pre.appendChild(elt("span", [content], fullStyle)); - } - builder.pre.appendChild(content); - } - - function buildTokenMeasure(builder, text, style, startStyle, endStyle) { - var wrapping = builder.cm.options.lineWrapping; - for (var i = 0; i < text.length; ++i) { - var ch = text.charAt(i), start = i == 0; - if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) { - ch = text.slice(i, i + 2); - ++i; - } else if (i && wrapping && spanAffectsWrapping(text, i)) { - builder.pre.appendChild(elt("wbr")); - } - var span = builder.measure[builder.pos] = - buildToken(builder, ch, style, - start && startStyle, i == text.length - 1 && endStyle); - // In IE single-space nodes wrap differently than spaces - // embedded in larger text nodes, except when set to - // white-space: normal (issue #1268). - if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) && - i < text.length - 1 && !/\s/.test(text.charAt(i + 1))) - span.style.whiteSpace = "normal"; - builder.pos += ch.length; - } - if (text.length) builder.measuredSomething = true; - } - - function buildTokenSplitSpaces(inner) { - function split(old) { - var out = " "; - for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; - out += " "; - return out; - } - return function(builder, text, style, startStyle, endStyle) { - return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle); - }; - } - - function buildCollapsedSpan(builder, size, widget) { - if (widget) { - if (!builder.display) widget = widget.cloneNode(true); - if (builder.measure) { - builder.measure[builder.pos] = size ? widget - : builder.pre.appendChild(zeroWidthElement(builder.cm.display.measure)); - builder.measuredSomething = true; - } - builder.pre.appendChild(widget); - } - builder.pos += size; - } - - // Outputs a number of spans to make up a line, taking highlighting - // and marked text into account. - function insertLineContent(line, builder, styles) { - var spans = line.markedSpans, allText = line.text, at = 0; - if (!spans) { - for (var i = 1; i < styles.length; i+=2) - builder.addToken(builder, allText.slice(at, at = styles[i]), styleToClass(styles[i+1])); - return; - } - - var len = allText.length, pos = 0, i = 1, text = "", style; - var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed; - for (;;) { - if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = ""; - collapsed = null; nextChange = Infinity; - var foundBookmark = null; - for (var j = 0; j < spans.length; ++j) { - var sp = spans[j], m = sp.marker; - if (sp.from <= pos && (sp.to == null || sp.to > pos)) { - if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } - if (m.className) spanStyle += " " + m.className; - if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; - if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; - if (m.collapsed && (!collapsed || collapsed.marker.size < m.size)) - collapsed = sp; - } else if (sp.from > pos && nextChange > sp.from) { - nextChange = sp.from; - } - if (m.type == "bookmark" && sp.from == pos && m.replacedWith) - foundBookmark = m.replacedWith; - } - if (collapsed && (collapsed.from || 0) == pos) { - buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos, - collapsed.from != null && collapsed.marker.replacedWith); - if (collapsed.to == null) return collapsed.marker.find(); - } - if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark); - } - if (pos >= len) break; - - var upto = Math.min(len, nextChange); - while (true) { - if (text) { - var end = pos + text.length; - if (!collapsed) { - var tokenText = end > upto ? text.slice(0, upto - pos) : text; - builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, - spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : ""); - } - if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} - pos = end; - spanStartStyle = ""; - } - text = allText.slice(at, at = styles[i++]); - style = styleToClass(styles[i++]); - } - } - } - - // DOCUMENT DATA STRUCTURE - - function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) { - function spansFor(n) {return markedSpans ? markedSpans[n] : null;} - function update(line, text, spans) { - updateLine(line, text, spans, estimateHeight); - signalLater(line, "change", line, change); - } - - var from = change.from, to = change.to, text = change.text; - var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); - var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; - - // First adjust the line structure - if (from.ch == 0 && to.ch == 0 && lastText == "") { - // This is a whole-line replace. Treated specially to make - // sure line objects move the way they are supposed to. - for (var i = 0, e = text.length - 1, added = []; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); - update(lastLine, lastLine.text, lastSpans); - if (nlines) doc.remove(from.line, nlines); - if (added.length) doc.insert(from.line, added); - } else if (firstLine == lastLine) { - if (text.length == 1) { - update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); - } else { - for (var added = [], i = 1, e = text.length - 1; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); - added.push(makeLine(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); - update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); - doc.insert(from.line + 1, added); - } - } else if (text.length == 1) { - update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); - doc.remove(from.line + 1, nlines); - } else { - update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); - update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); - for (var i = 1, e = text.length - 1, added = []; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); - if (nlines > 1) doc.remove(from.line + 1, nlines - 1); - doc.insert(from.line + 1, added); - } - - signalLater(doc, "change", doc, change); - setSelection(doc, selAfter.anchor, selAfter.head, null, true); - } - - function LeafChunk(lines) { - this.lines = lines; - this.parent = null; - for (var i = 0, e = lines.length, height = 0; i < e; ++i) { - lines[i].parent = this; - height += lines[i].height; - } - this.height = height; - } - - LeafChunk.prototype = { - chunkSize: function() { return this.lines.length; }, - removeInner: function(at, n) { - for (var i = at, e = at + n; i < e; ++i) { - var line = this.lines[i]; - this.height -= line.height; - cleanUpLine(line); - signalLater(line, "delete"); - } - this.lines.splice(at, n); - }, - collapse: function(lines) { - lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); - }, - insertInner: function(at, lines, height) { - this.height += height; - this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); - for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; - }, - iterN: function(at, n, op) { - for (var e = at + n; at < e; ++at) - if (op(this.lines[at])) return true; - } - }; - - function BranchChunk(children) { - this.children = children; - var size = 0, height = 0; - for (var i = 0, e = children.length; i < e; ++i) { - var ch = children[i]; - size += ch.chunkSize(); height += ch.height; - ch.parent = this; - } - this.size = size; - this.height = height; - this.parent = null; - } - - BranchChunk.prototype = { - chunkSize: function() { return this.size; }, - removeInner: function(at, n) { - this.size -= n; - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var rm = Math.min(n, sz - at), oldHeight = child.height; - child.removeInner(at, rm); - this.height -= oldHeight - child.height; - if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } - if ((n -= rm) == 0) break; - at = 0; - } else at -= sz; - } - if (this.size - n < 25) { - var lines = []; - this.collapse(lines); - this.children = [new LeafChunk(lines)]; - this.children[0].parent = this; - } - }, - collapse: function(lines) { - for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); - }, - insertInner: function(at, lines, height) { - this.size += lines.length; - this.height += height; - for (var i = 0, e = this.children.length; i < e; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at <= sz) { - child.insertInner(at, lines, height); - if (child.lines && child.lines.length > 50) { - while (child.lines.length > 50) { - var spilled = child.lines.splice(child.lines.length - 25, 25); - var newleaf = new LeafChunk(spilled); - child.height -= newleaf.height; - this.children.splice(i + 1, 0, newleaf); - newleaf.parent = this; - } - this.maybeSpill(); - } - break; - } - at -= sz; - } - }, - maybeSpill: function() { - if (this.children.length <= 10) return; - var me = this; - do { - var spilled = me.children.splice(me.children.length - 5, 5); - var sibling = new BranchChunk(spilled); - if (!me.parent) { // Become the parent node - var copy = new BranchChunk(me.children); - copy.parent = me; - me.children = [copy, sibling]; - me = copy; - } else { - me.size -= sibling.size; - me.height -= sibling.height; - var myIndex = indexOf(me.parent.children, me); - me.parent.children.splice(myIndex + 1, 0, sibling); - } - sibling.parent = me.parent; - } while (me.children.length > 10); - me.parent.maybeSpill(); - }, - iterN: function(at, n, op) { - for (var i = 0, e = this.children.length; i < e; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var used = Math.min(n, sz - at); - if (child.iterN(at, used, op)) return true; - if ((n -= used) == 0) break; - at = 0; - } else at -= sz; - } - } - }; - - var nextDocId = 0; - var Doc = CodeMirror.Doc = function(text, mode, firstLine) { - if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); - if (firstLine == null) firstLine = 0; - - BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]); - this.first = firstLine; - this.scrollTop = this.scrollLeft = 0; - this.cantEdit = false; - this.history = makeHistory(); - this.cleanGeneration = 1; - this.frontier = firstLine; - var start = Pos(firstLine, 0); - this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null}; - this.id = ++nextDocId; - this.modeOption = mode; - - if (typeof text == "string") text = splitLines(text); - updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start}); - }; - - Doc.prototype = createObj(BranchChunk.prototype, { - constructor: Doc, - iter: function(from, to, op) { - if (op) this.iterN(from - this.first, to - from, op); - else this.iterN(this.first, this.first + this.size, from); - }, - - insert: function(at, lines) { - var height = 0; - for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; - this.insertInner(at - this.first, lines, height); - }, - remove: function(at, n) { this.removeInner(at - this.first, n); }, - - getValue: function(lineSep) { - var lines = getLines(this, this.first, this.first + this.size); - if (lineSep === false) return lines; - return lines.join(lineSep || "\n"); - }, - setValue: function(code) { - var top = Pos(this.first, 0), last = this.first + this.size - 1; - makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), - text: splitLines(code), origin: "setValue"}, - {head: top, anchor: top}, true); - }, - replaceRange: function(code, from, to, origin) { - from = clipPos(this, from); - to = to ? clipPos(this, to) : from; - replaceRange(this, code, from, to, origin); - }, - getRange: function(from, to, lineSep) { - var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); - if (lineSep === false) return lines; - return lines.join(lineSep || "\n"); - }, - - getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, - setLine: function(line, text) { - if (isLine(this, line)) - replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line))); - }, - removeLine: function(line) { - if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line))); - else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0))); - }, - - getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, - getLineNumber: function(line) {return lineNo(line);}, - - lineCount: function() {return this.size;}, - firstLine: function() {return this.first;}, - lastLine: function() {return this.first + this.size - 1;}, - - clipPos: function(pos) {return clipPos(this, pos);}, - - getCursor: function(start) { - var sel = this.sel, pos; - if (start == null || start == "head") pos = sel.head; - else if (start == "anchor") pos = sel.anchor; - else if (start == "end" || start === false) pos = sel.to; - else pos = sel.from; - return copyPos(pos); - }, - somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);}, - - setCursor: docOperation(function(line, ch, extend) { - var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line); - if (extend) extendSelection(this, pos); - else setSelection(this, pos, pos); - }), - setSelection: docOperation(function(anchor, head) { - setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor)); - }), - extendSelection: docOperation(function(from, to) { - extendSelection(this, clipPos(this, from), to && clipPos(this, to)); - }), - - getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);}, - replaceSelection: function(code, collapse, origin) { - makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around"); - }, - undo: docOperation(function() {makeChangeFromHistory(this, "undo");}), - redo: docOperation(function() {makeChangeFromHistory(this, "redo");}), - - setExtending: function(val) {this.sel.extend = val;}, - - historySize: function() { - var hist = this.history; - return {undo: hist.done.length, redo: hist.undone.length}; - }, - clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);}, - - markClean: function() { - this.cleanGeneration = this.changeGeneration(); - }, - changeGeneration: function() { - this.history.lastOp = this.history.lastOrigin = null; - return this.history.generation; - }, - isClean: function (gen) { - return this.history.generation == (gen || this.cleanGeneration); - }, - - getHistory: function() { - return {done: copyHistoryArray(this.history.done), - undone: copyHistoryArray(this.history.undone)}; - }, - setHistory: function(histData) { - var hist = this.history = makeHistory(this.history.maxGeneration); - hist.done = histData.done.slice(0); - hist.undone = histData.undone.slice(0); - }, - - markText: function(from, to, options) { - return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); - }, - setBookmark: function(pos, options) { - var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), - insertLeft: options && options.insertLeft}; - pos = clipPos(this, pos); - return markText(this, pos, pos, realOpts, "bookmark"); - }, - findMarksAt: function(pos) { - pos = clipPos(this, pos); - var markers = [], spans = getLine(this, pos.line).markedSpans; - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if ((span.from == null || span.from <= pos.ch) && - (span.to == null || span.to >= pos.ch)) - markers.push(span.marker.parent || span.marker); - } - return markers; - }, - getAllMarks: function() { - var markers = []; - this.iter(function(line) { - var sps = line.markedSpans; - if (sps) for (var i = 0; i < sps.length; ++i) - if (sps[i].from != null) markers.push(sps[i].marker); - }); - return markers; - }, - - posFromIndex: function(off) { - var ch, lineNo = this.first; - this.iter(function(line) { - var sz = line.text.length + 1; - if (sz > off) { ch = off; return true; } - off -= sz; - ++lineNo; - }); - return clipPos(this, Pos(lineNo, ch)); - }, - indexFromPos: function (coords) { - coords = clipPos(this, coords); - var index = coords.ch; - if (coords.line < this.first || coords.ch < 0) return 0; - this.iter(this.first, coords.line, function (line) { - index += line.text.length + 1; - }); - return index; - }, - - copy: function(copyHistory) { - var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); - doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; - doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor, - shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn}; - if (copyHistory) { - doc.history.undoDepth = this.history.undoDepth; - doc.setHistory(this.getHistory()); - } - return doc; - }, - - linkedDoc: function(options) { - if (!options) options = {}; - var from = this.first, to = this.first + this.size; - if (options.from != null && options.from > from) from = options.from; - if (options.to != null && options.to < to) to = options.to; - var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); - if (options.sharedHist) copy.history = this.history; - (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); - copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; - return copy; - }, - unlinkDoc: function(other) { - if (other instanceof CodeMirror) other = other.doc; - if (this.linked) for (var i = 0; i < this.linked.length; ++i) { - var link = this.linked[i]; - if (link.doc != other) continue; - this.linked.splice(i, 1); - other.unlinkDoc(this); - break; - } - // If the histories were shared, split them again - if (other.history == this.history) { - var splitIds = [other.id]; - linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); - other.history = makeHistory(); - other.history.done = copyHistoryArray(this.history.done, splitIds); - other.history.undone = copyHistoryArray(this.history.undone, splitIds); - } - }, - iterLinkedDocs: function(f) {linkedDocs(this, f);}, - - getMode: function() {return this.mode;}, - getEditor: function() {return this.cm;} - }); - - Doc.prototype.eachLine = Doc.prototype.iter; - - // The Doc methods that should be available on CodeMirror instances - var dontDelegate = "iter insert remove copy getEditor".split(" "); - for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) - CodeMirror.prototype[prop] = (function(method) { - return function() {return method.apply(this.doc, arguments);}; - })(Doc.prototype[prop]); - - function linkedDocs(doc, f, sharedHistOnly) { - function propagate(doc, skip, sharedHist) { - if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { - var rel = doc.linked[i]; - if (rel.doc == skip) continue; - var shared = sharedHist && rel.sharedHist; - if (sharedHistOnly && !shared) continue; - f(rel.doc, shared); - propagate(rel.doc, doc, shared); - } - } - propagate(doc, null, true); - } - - function attachDoc(cm, doc) { - if (doc.cm) throw new Error("This document is already in use."); - cm.doc = doc; - doc.cm = cm; - estimateLineHeights(cm); - loadMode(cm); - if (!cm.options.lineWrapping) computeMaxLength(cm); - cm.options.mode = doc.modeOption; - regChange(cm); - } - - // LINE UTILITIES - - function getLine(chunk, n) { - n -= chunk.first; - while (!chunk.lines) { - for (var i = 0;; ++i) { - var child = chunk.children[i], sz = child.chunkSize(); - if (n < sz) { chunk = child; break; } - n -= sz; - } - } - return chunk.lines[n]; - } - - function getBetween(doc, start, end) { - var out = [], n = start.line; - doc.iter(start.line, end.line + 1, function(line) { - var text = line.text; - if (n == end.line) text = text.slice(0, end.ch); - if (n == start.line) text = text.slice(start.ch); - out.push(text); - ++n; - }); - return out; - } - function getLines(doc, from, to) { - var out = []; - doc.iter(from, to, function(line) { out.push(line.text); }); - return out; - } - - function updateLineHeight(line, height) { - var diff = height - line.height; - for (var n = line; n; n = n.parent) n.height += diff; - } - - function lineNo(line) { - if (line.parent == null) return null; - var cur = line.parent, no = indexOf(cur.lines, line); - for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { - for (var i = 0;; ++i) { - if (chunk.children[i] == cur) break; - no += chunk.children[i].chunkSize(); - } - } - return no + cur.first; - } - - function lineAtHeight(chunk, h) { - var n = chunk.first; - outer: do { - for (var i = 0, e = chunk.children.length; i < e; ++i) { - var child = chunk.children[i], ch = child.height; - if (h < ch) { chunk = child; continue outer; } - h -= ch; - n += child.chunkSize(); - } - return n; - } while (!chunk.lines); - for (var i = 0, e = chunk.lines.length; i < e; ++i) { - var line = chunk.lines[i], lh = line.height; - if (h < lh) break; - h -= lh; - } - return n + i; - } - - function heightAtLine(cm, lineObj) { - lineObj = visualLine(cm.doc, lineObj); - - var h = 0, chunk = lineObj.parent; - for (var i = 0; i < chunk.lines.length; ++i) { - var line = chunk.lines[i]; - if (line == lineObj) break; - else h += line.height; - } - for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { - for (var i = 0; i < p.children.length; ++i) { - var cur = p.children[i]; - if (cur == chunk) break; - else h += cur.height; - } - } - return h; - } - - function getOrder(line) { - var order = line.order; - if (order == null) order = line.order = bidiOrdering(line.text); - return order; - } - - // HISTORY - - function makeHistory(startGen) { - return { - // Arrays of history events. Doing something adds an event to - // done and clears undo. Undoing moves events from done to - // undone, redoing moves them in the other direction. - done: [], undone: [], undoDepth: Infinity, - // Used to track when changes can be merged into a single undo - // event - lastTime: 0, lastOp: null, lastOrigin: null, - // Used by the isClean() method - generation: startGen || 1, maxGeneration: startGen || 1 - }; - } - - function attachLocalSpans(doc, change, from, to) { - var existing = change["spans_" + doc.id], n = 0; - doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { - if (line.markedSpans) - (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; - ++n; - }); - } - - function historyChangeFromChange(doc, change) { - var histChange = {from: change.from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; - attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); - linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); - return histChange; - } - - function addToHistory(doc, change, selAfter, opId) { - var hist = doc.history; - hist.undone.length = 0; - var time = +new Date, cur = lst(hist.done); - - if (cur && - (hist.lastOp == opId || - hist.lastOrigin == change.origin && change.origin && - ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) || - change.origin.charAt(0) == "*"))) { - // Merge this change into the last event - var last = lst(cur.changes); - if (posEq(change.from, change.to) && posEq(change.from, last.to)) { - // Optimized case for simple insertion -- don't want to add - // new changesets for every character typed - last.to = changeEnd(change); - } else { - // Add new sub-event - cur.changes.push(historyChangeFromChange(doc, change)); - } - cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head; - } else { - // Can not be merged, start a new event. - cur = {changes: [historyChangeFromChange(doc, change)], - generation: hist.generation, - anchorBefore: doc.sel.anchor, headBefore: doc.sel.head, - anchorAfter: selAfter.anchor, headAfter: selAfter.head}; - hist.done.push(cur); - hist.generation = ++hist.maxGeneration; - while (hist.done.length > hist.undoDepth) - hist.done.shift(); - } - hist.lastTime = time; - hist.lastOp = opId; - hist.lastOrigin = change.origin; - } - - function removeClearedSpans(spans) { - if (!spans) return null; - for (var i = 0, out; i < spans.length; ++i) { - if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } - else if (out) out.push(spans[i]); - } - return !out ? spans : out.length ? out : null; - } - - function getOldSpans(doc, change) { - var found = change["spans_" + doc.id]; - if (!found) return null; - for (var i = 0, nw = []; i < change.text.length; ++i) - nw.push(removeClearedSpans(found[i])); - return nw; - } - - // Used both to provide a JSON-safe object in .getHistory, and, when - // detaching a document, to split the history in two - function copyHistoryArray(events, newGroup) { - for (var i = 0, copy = []; i < events.length; ++i) { - var event = events[i], changes = event.changes, newChanges = []; - copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore, - anchorAfter: event.anchorAfter, headAfter: event.headAfter}); - for (var j = 0; j < changes.length; ++j) { - var change = changes[j], m; - newChanges.push({from: change.from, to: change.to, text: change.text}); - if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { - if (indexOf(newGroup, Number(m[1])) > -1) { - lst(newChanges)[prop] = change[prop]; - delete change[prop]; - } - } - } - } - return copy; - } - - // Rebasing/resetting history to deal with externally-sourced changes - - function rebaseHistSel(pos, from, to, diff) { - if (to < pos.line) { - pos.line += diff; - } else if (from < pos.line) { - pos.line = from; - pos.ch = 0; - } - } - - // Tries to rebase an array of history events given a change in the - // document. If the change touches the same lines as the event, the - // event, and everything 'behind' it, is discarded. If the change is - // before the event, the event's positions are updated. Uses a - // copy-on-write scheme for the positions, to avoid having to - // reallocate them all on every rebase, but also avoid problems with - // shared position objects being unsafely updated. - function rebaseHistArray(array, from, to, diff) { - for (var i = 0; i < array.length; ++i) { - var sub = array[i], ok = true; - for (var j = 0; j < sub.changes.length; ++j) { - var cur = sub.changes[j]; - if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); } - if (to < cur.from.line) { - cur.from.line += diff; - cur.to.line += diff; - } else if (from <= cur.to.line) { - ok = false; - break; - } - } - if (!sub.copied) { - sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore); - sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter); - sub.copied = true; - } - if (!ok) { - array.splice(0, i + 1); - i = 0; - } else { - rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore); - rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter); - } - } - } - - function rebaseHist(hist, change) { - var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; - rebaseHistArray(hist.done, from, to, diff); - rebaseHistArray(hist.undone, from, to, diff); - } - - // EVENT OPERATORS - - function stopMethod() {e_stop(this);} - // Ensure an event has a stop method. - function addStop(event) { - if (!event.stop) event.stop = stopMethod; - return event; - } - - function e_preventDefault(e) { - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - } - function e_stopPropagation(e) { - if (e.stopPropagation) e.stopPropagation(); - else e.cancelBubble = true; - } - function e_defaultPrevented(e) { - return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; - } - function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} - CodeMirror.e_stop = e_stop; - CodeMirror.e_preventDefault = e_preventDefault; - CodeMirror.e_stopPropagation = e_stopPropagation; - - function e_target(e) {return e.target || e.srcElement;} - function e_button(e) { - var b = e.which; - if (b == null) { - if (e.button & 1) b = 1; - else if (e.button & 2) b = 3; - else if (e.button & 4) b = 2; - } - if (mac && e.ctrlKey && b == 1) b = 3; - return b; - } - - // EVENT HANDLING - - function on(emitter, type, f) { - if (emitter.addEventListener) - emitter.addEventListener(type, f, false); - else if (emitter.attachEvent) - emitter.attachEvent("on" + type, f); - else { - var map = emitter._handlers || (emitter._handlers = {}); - var arr = map[type] || (map[type] = []); - arr.push(f); - } - } - - function off(emitter, type, f) { - if (emitter.removeEventListener) - emitter.removeEventListener(type, f, false); - else if (emitter.detachEvent) - emitter.detachEvent("on" + type, f); - else { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; - for (var i = 0; i < arr.length; ++i) - if (arr[i] == f) { arr.splice(i, 1); break; } - } - } - - function signal(emitter, type /*, values...*/) { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; - var args = Array.prototype.slice.call(arguments, 2); - for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); - } - - var delayedCallbacks, delayedCallbackDepth = 0; - function signalLater(emitter, type /*, values...*/) { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; - var args = Array.prototype.slice.call(arguments, 2); - if (!delayedCallbacks) { - ++delayedCallbackDepth; - delayedCallbacks = []; - setTimeout(fireDelayed, 0); - } - function bnd(f) {return function(){f.apply(null, args);};}; - for (var i = 0; i < arr.length; ++i) - delayedCallbacks.push(bnd(arr[i])); - } - - function signalDOMEvent(cm, e) { - signal(cm, e.type, cm, e); - return e_defaultPrevented(e); - } - - function fireDelayed() { - --delayedCallbackDepth; - var delayed = delayedCallbacks; - delayedCallbacks = null; - for (var i = 0; i < delayed.length; ++i) delayed[i](); - } - - function hasHandler(emitter, type) { - var arr = emitter._handlers && emitter._handlers[type]; - return arr && arr.length > 0; - } - - CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal; - - // MISC UTILITIES - - // Number of pixels added to scroller and sizer to hide scrollbar - var scrollerCutOff = 30; - - // Returned or thrown by various protocols to signal 'I'm not - // handling this'. - var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; - - function Delayed() {this.id = null;} - Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; - - // Counts the column offset in a string, taking tabs into account. - // Used mostly to find indentation. - function countColumn(string, end, tabSize, startIndex, startValue) { - if (end == null) { - end = string.search(/[^\s\u00a0]/); - if (end == -1) end = string.length; - } - for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) { - if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); - else ++n; - } - return n; - } - CodeMirror.countColumn = countColumn; - - var spaceStrs = [""]; - function spaceStr(n) { - while (spaceStrs.length <= n) - spaceStrs.push(lst(spaceStrs) + " "); - return spaceStrs[n]; - } - - function lst(arr) { return arr[arr.length-1]; } - - function selectInput(node) { - if (ios) { // Mobile Safari apparently has a bug where select() is broken. - node.selectionStart = 0; - node.selectionEnd = node.value.length; - } else { - // Suppress mysterious IE10 errors - try { node.select(); } - catch(_e) {} - } - } - - function indexOf(collection, elt) { - if (collection.indexOf) return collection.indexOf(elt); - for (var i = 0, e = collection.length; i < e; ++i) - if (collection[i] == elt) return i; - return -1; - } - - function createObj(base, props) { - function Obj() {} - Obj.prototype = base; - var inst = new Obj(); - if (props) copyObj(props, inst); - return inst; - } - - function copyObj(obj, target) { - if (!target) target = {}; - for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; - return target; - } - - function emptyArray(size) { - for (var a = [], i = 0; i < size; ++i) a.push(undefined); - return a; - } - - function bind(f) { - var args = Array.prototype.slice.call(arguments, 1); - return function(){return f.apply(null, args);}; - } - - var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; - function isWordChar(ch) { - return /\w/.test(ch) || ch > "\x80" && - (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); - } - - function isEmpty(obj) { - for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; - return true; - } - - var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/; - - // DOM UTILITIES - - function elt(tag, content, className, style) { - var e = document.createElement(tag); - if (className) e.className = className; - if (style) e.style.cssText = style; - if (typeof content == "string") setTextContent(e, content); - else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); - return e; - } - - function removeChildren(e) { - for (var count = e.childNodes.length; count > 0; --count) - e.removeChild(e.firstChild); - return e; - } - - function removeChildrenAndAdd(parent, e) { - return removeChildren(parent).appendChild(e); - } - - function setTextContent(e, str) { - if (ie_lt9) { - e.innerHTML = ""; - e.appendChild(document.createTextNode(str)); - } else e.textContent = str; - } - - function getRect(node) { - return node.getBoundingClientRect(); - } - CodeMirror.replaceGetRect = function(f) { getRect = f; }; - - // FEATURE DETECTION - - // Detect drag-and-drop - var dragAndDrop = function() { - // There is *some* kind of drag-and-drop support in IE6-8, but I - // couldn't get it to work yet. - if (ie_lt9) return false; - var div = elt('div'); - return "draggable" in div || "dragDrop" in div; - }(); - - // For a reason I have yet to figure out, some browsers disallow - // word wrapping between certain characters *only* if a new inline - // element is started between them. This makes it hard to reliably - // measure the position of things, since that requires inserting an - // extra span. This terribly fragile set of tests matches the - // character combinations that suffer from this phenomenon on the - // various browsers. - function spanAffectsWrapping() { return false; } - if (gecko) // Only for "$'" - spanAffectsWrapping = function(str, i) { - return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39; - }; - else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)) - spanAffectsWrapping = function(str, i) { - return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1)); - }; - else if (webkit) - spanAffectsWrapping = function(str, i) { - if (i > 1 && str.charCodeAt(i - 1) == 45 && /\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) - return true; - return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); - }; - - var knownScrollbarWidth; - function scrollbarWidth(measure) { - if (knownScrollbarWidth != null) return knownScrollbarWidth; - var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll"); - removeChildrenAndAdd(measure, test); - if (test.offsetWidth) - knownScrollbarWidth = test.offsetHeight - test.clientHeight; - return knownScrollbarWidth || 0; - } - - var zwspSupported; - function zeroWidthElement(measure) { - if (zwspSupported == null) { - var test = elt("span", "\u200b"); - removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); - if (measure.firstChild.offsetHeight != 0) - zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8; - } - if (zwspSupported) return elt("span", "\u200b"); - else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); - } - - // See if "".split is the broken IE version, if so, provide an - // alternative way to split lines. - var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { - var pos = 0, result = [], l = string.length; - while (pos <= l) { - var nl = string.indexOf("\n", pos); - if (nl == -1) nl = string.length; - var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); - var rt = line.indexOf("\r"); - if (rt != -1) { - result.push(line.slice(0, rt)); - pos += rt + 1; - } else { - result.push(line); - pos = nl + 1; - } - } - return result; - } : function(string){return string.split(/\r\n?|\n/);}; - CodeMirror.splitLines = splitLines; - - var hasSelection = window.getSelection ? function(te) { - try { return te.selectionStart != te.selectionEnd; } - catch(e) { return false; } - } : function(te) { - try {var range = te.ownerDocument.selection.createRange();} - catch(e) {} - if (!range || range.parentElement() != te) return false; - return range.compareEndPoints("StartToEnd", range) != 0; - }; - - var hasCopyEvent = (function() { - var e = elt("div"); - if ("oncopy" in e) return true; - e.setAttribute("oncopy", "return;"); - return typeof e.oncopy == 'function'; - })(); - - // KEY NAMING - - var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", - 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", - 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", - 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", - 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", - 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; - CodeMirror.keyNames = keyNames; - (function() { - // Number keys - for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); - // Alphabetic keys - for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); - // Function keys - for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; - })(); - - // BIDI HELPERS - - function iterateBidiSections(order, from, to, f) { - if (!order) return f(from, to, "ltr"); - for (var i = 0; i < order.length; ++i) { - var part = order[i]; - if (part.from < to && part.to > from || from == to && part.to == from) - f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); - } - } - - function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } - function bidiRight(part) { return part.level % 2 ? part.from : part.to; } - - function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } - function lineRight(line) { - var order = getOrder(line); - if (!order) return line.text.length; - return bidiRight(lst(order)); - } - - function lineStart(cm, lineN) { - var line = getLine(cm.doc, lineN); - var visual = visualLine(cm.doc, line); - if (visual != line) lineN = lineNo(visual); - var order = getOrder(visual); - var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); - return Pos(lineN, ch); - } - function lineEnd(cm, lineN) { - var merged, line; - while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN))) - lineN = merged.find().to.line; - var order = getOrder(line); - var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); - return Pos(lineN, ch); - } - - function compareBidiLevel(order, a, b) { - var linedir = order[0].level; - if (a == linedir) return true; - if (b == linedir) return false; - return a < b; - } - var bidiOther; - function getBidiPartAt(order, pos) { - for (var i = 0, found; i < order.length; ++i) { - var cur = order[i]; - if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; } - if (cur.from == pos || cur.to == pos) { - if (found == null) { - found = i; - } else if (compareBidiLevel(order, cur.level, order[found].level)) { - bidiOther = found; - return i; - } else { - bidiOther = i; - return found; - } - } - } - bidiOther = null; - return found; - } - - function moveInLine(line, pos, dir, byUnit) { - if (!byUnit) return pos + dir; - do pos += dir; - while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); - return pos; - } - - // This is somewhat involved. It is needed in order to move - // 'visually' through bi-directional text -- i.e., pressing left - // should make the cursor go left, even when in RTL text. The - // tricky part is the 'jumps', where RTL and LTR text touch each - // other. This often requires the cursor offset to move more than - // one unit, in order to visually move one unit. - function moveVisually(line, start, dir, byUnit) { - var bidi = getOrder(line); - if (!bidi) return moveLogically(line, start, dir, byUnit); - var pos = getBidiPartAt(bidi, start), part = bidi[pos]; - var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); - - for (;;) { - if (target > part.from && target < part.to) return target; - if (target == part.from || target == part.to) { - if (getBidiPartAt(bidi, target) == pos) return target; - part = bidi[pos += dir]; - return (dir > 0) == part.level % 2 ? part.to : part.from; - } else { - part = bidi[pos += dir]; - if (!part) return null; - if ((dir > 0) == part.level % 2) - target = moveInLine(line, part.to, -1, byUnit); - else - target = moveInLine(line, part.from, 1, byUnit); - } - } - } - - function moveLogically(line, start, dir, byUnit) { - var target = start + dir; - if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir; - return target < 0 || target > line.text.length ? null : target; - } - - // Bidirectional ordering algorithm - // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm - // that this (partially) implements. - - // One-char codes used for character types: - // L (L): Left-to-Right - // R (R): Right-to-Left - // r (AL): Right-to-Left Arabic - // 1 (EN): European Number - // + (ES): European Number Separator - // % (ET): European Number Terminator - // n (AN): Arabic Number - // , (CS): Common Number Separator - // m (NSM): Non-Spacing Mark - // b (BN): Boundary Neutral - // s (B): Paragraph Separator - // t (S): Segment Separator - // w (WS): Whitespace - // N (ON): Other Neutrals - - // Returns null if characters are ordered as they appear - // (left-to-right), or an array of sections ({from, to, level} - // objects) in the order in which they occur visually. - var bidiOrdering = (function() { - // Character types for codepoints 0 to 0xff - var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL"; - // Character types for codepoints 0x600 to 0x6ff - var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr"; - function charType(code) { - if (code <= 0xff) return lowTypes.charAt(code); - else if (0x590 <= code && code <= 0x5f4) return "R"; - else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600); - else if (0x700 <= code && code <= 0x8ac) return "r"; - else return "L"; - } - - var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; - var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; - // Browsers seem to always treat the boundaries of block elements as being L. - var outerType = "L"; - - return function(str) { - if (!bidiRE.test(str)) return false; - var len = str.length, types = []; - for (var i = 0, type; i < len; ++i) - types.push(type = charType(str.charCodeAt(i))); - - // W1. Examine each non-spacing mark (NSM) in the level run, and - // change the type of the NSM to the type of the previous - // character. If the NSM is at the start of the level run, it will - // get the type of sor. - for (var i = 0, prev = outerType; i < len; ++i) { - var type = types[i]; - if (type == "m") types[i] = prev; - else prev = type; - } - - // W2. Search backwards from each instance of a European number - // until the first strong type (R, L, AL, or sor) is found. If an - // AL is found, change the type of the European number to Arabic - // number. - // W3. Change all ALs to R. - for (var i = 0, cur = outerType; i < len; ++i) { - var type = types[i]; - if (type == "1" && cur == "r") types[i] = "n"; - else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } - } - - // W4. A single European separator between two European numbers - // changes to a European number. A single common separator between - // two numbers of the same type changes to that type. - for (var i = 1, prev = types[0]; i < len - 1; ++i) { - var type = types[i]; - if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; - else if (type == "," && prev == types[i+1] && - (prev == "1" || prev == "n")) types[i] = prev; - prev = type; - } - - // W5. A sequence of European terminators adjacent to European - // numbers changes to all European numbers. - // W6. Otherwise, separators and terminators change to Other - // Neutral. - for (var i = 0; i < len; ++i) { - var type = types[i]; - if (type == ",") types[i] = "N"; - else if (type == "%") { - for (var end = i + 1; end < len && types[end] == "%"; ++end) {} - var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // W7. Search backwards from each instance of a European number - // until the first strong type (R, L, or sor) is found. If an L is - // found, then change the type of the European number to L. - for (var i = 0, cur = outerType; i < len; ++i) { - var type = types[i]; - if (cur == "L" && type == "1") types[i] = "L"; - else if (isStrong.test(type)) cur = type; - } - - // N1. A sequence of neutrals takes the direction of the - // surrounding strong text if the text on both sides has the same - // direction. European and Arabic numbers act as if they were R in - // terms of their influence on neutrals. Start-of-level-run (sor) - // and end-of-level-run (eor) are used at level run boundaries. - // N2. Any remaining neutrals take the embedding direction. - for (var i = 0; i < len; ++i) { - if (isNeutral.test(types[i])) { - for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} - var before = (i ? types[i-1] : outerType) == "L"; - var after = (end < len - 1 ? types[end] : outerType) == "L"; - var replace = before || after ? "L" : "R"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // Here we depart from the documented algorithm, in order to avoid - // building up an actual levels array. Since there are only three - // levels (0, 1, 2) in an implementation that doesn't take - // explicit embedding into account, we can build up the order on - // the fly, without following the level-based algorithm. - var order = [], m; - for (var i = 0; i < len;) { - if (countsAsLeft.test(types[i])) { - var start = i; - for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} - order.push({from: start, to: i, level: 0}); - } else { - var pos = i, at = order.length; - for (++i; i < len && types[i] != "L"; ++i) {} - for (var j = pos; j < i;) { - if (countsAsNum.test(types[j])) { - if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1}); - var nstart = j; - for (++j; j < i && countsAsNum.test(types[j]); ++j) {} - order.splice(at, 0, {from: nstart, to: j, level: 2}); - pos = j; - } else ++j; - } - if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1}); - } - } - if (order[0].level == 1 && (m = str.match(/^\s+/))) { - order[0].from = m[0].length; - order.unshift({from: 0, to: m[0].length, level: 0}); - } - if (lst(order).level == 1 && (m = str.match(/\s+$/))) { - lst(order).to -= m[0].length; - order.push({from: len - m[0].length, to: len, level: 0}); - } - if (order[0].level != lst(order).level) - order.push({from: len, to: len, level: order[0].level}); - - return order; - }; - })(); - - // THE END - - CodeMirror.version = "3.14.0"; - - return CodeMirror; -})(); diff --git a/koha-tmpl/intranet-tmpl/lib/codemirror/lib/runmode.js b/koha-tmpl/intranet-tmpl/lib/codemirror/lib/runmode.js deleted file mode 100644 index a7da6d7..0000000 --- a/koha-tmpl/intranet-tmpl/lib/codemirror/lib/runmode.js +++ /dev/null @@ -1,56 +0,0 @@ -CodeMirror.runMode = function(string, modespec, callback, options) { - var mode = CodeMirror.getMode(CodeMirror.defaults, modespec); - var ie = /MSIE \d/.test(navigator.userAgent); - var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); - - if (callback.nodeType == 1) { - var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; - var node = callback, col = 0; - node.innerHTML = ""; - callback = function(text, style) { - if (text == "\n") { - // Emitting LF or CRLF on IE8 or earlier results in an incorrect display. - // Emitting a carriage return makes everything ok. - node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text)); - col = 0; - return; - } - var content = ""; - // replace tabs - for (var pos = 0;;) { - var idx = text.indexOf("\t", pos); - if (idx == -1) { - content += text.slice(pos); - col += text.length - pos; - break; - } else { - col += idx - pos; - content += text.slice(pos, idx); - var size = tabSize - col % tabSize; - col += size; - for (var i = 0; i < size; ++i) content += " "; - pos = idx + 1; - } - } - - if (style) { - var sp = node.appendChild(document.createElement("span")); - sp.className = "cm-" + style.replace(/ +/g, " cm-"); - sp.appendChild(document.createTextNode(content)); - } else { - node.appendChild(document.createTextNode(content)); - } - }; - } - - var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode); - for (var i = 0, e = lines.length; i < e; ++i) { - if (i) callback("\n"); - var stream = new CodeMirror.StringStream(lines[i]); - while (!stream.eol()) { - var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start); - stream.start = stream.pos; - } - } -}; diff --git a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/humanmsg.js b/koha-tmpl/intranet-tmpl/lib/jquery/plugins/humanmsg.js index 6b3be81..347d345 100644 --- a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/humanmsg.js +++ b/koha-tmpl/intranet-tmpl/lib/jquery/plugins/humanmsg.js @@ -29,9 +29,21 @@ var humanMsg = { }, displayAlert: function(msg, options) { - humanMsg.displayMsg('

' + msg + '

', $.extend({log: false}, options), true); + humanMsg.displayMsg('

' + msg + '

', options); }, + logMsg: function(msg) { + jQuery('#'+humanMsg.logID) + .show().children('ul').prepend('
  • '+msg+'
  • ') // Prepend message to log + .children('li:first').slideDown(200) // Slide it down + + if ( jQuery('#'+humanMsg.logID+' ul').css('display') == 'none') { + jQuery('#'+humanMsg.logID+' p').animate({ bottom: 40 }, 200, 'linear', function() { + jQuery(this).animate({ bottom: 0 }, 300, 'swing', function() { jQuery(this).css({ bottom: 0 }) }) + }) + } + }, + displayMsg: function(msg, options) { if (msg == '') return; @@ -51,17 +63,7 @@ var humanMsg = { // Show message jQuery('#'+humanMsg.msgID).attr('class', 'humanMsg ' + options.className).show().animate({ opacity: humanMsg.msgOpacity}, 200, function() { - if ( !options.log ) return true; - jQuery('#'+humanMsg.logID) - .show().children('ul').prepend('
  • '+msg+'
  • ') // Prepend message to log - .children('li:first').slideDown(200) // Slide it down - - if ( jQuery('#'+humanMsg.logID+' ul').css('display') == 'none') { - jQuery('#'+humanMsg.logID+' p').animate({ bottom: 40 }, 200, 'linear', function() { - jQuery(this).animate({ bottom: 0 }, 300, 'swing', function() { jQuery(this).css({ bottom: 0 }) }) - }) - } - + humanMsg.logMsg(msg, options); }) // Watch for mouse & keyboard in `delay` diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/koha-backend.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/koha-backend.js new file mode 100644 index 0000000..c70c3bf --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/koha-backend.js @@ -0,0 +1,201 @@ +define( [ '/cgi-bin/koha/svc/cataloguing/framework?frameworkcode=&callback=define', 'marc-record' ], function( defaultFramework, MARC ) { + var _authorised_values = defaultFramework.authorised_values; + var _frameworks = {}; + var _framework_mappings = {}; + + function _fromXMLStruct( data ) { + result = {}; + + $(data).children().eq(0).children().each( function() { + var $contents = $(this).contents(); + if ( $contents.length == 1 && $contents[0].nodeType == Node.TEXT_NODE ) { + result[ this.localName ] = $contents[0].data; + } else { + result[ this.localName ] = $contents.filter( function() { return this.nodeType != Node.TEXT_NODE || !this.data.match( /^\s+$/ ) } ).toArray(); + } + } ); + + return result; + } + + function _importFramework( frameworkcode, frameworkinfo ) { + _frameworks[frameworkcode] = frameworkinfo; + _framework_mappings[frameworkcode] = {}; + + $.each( frameworkinfo, function( i, tag ) { + var tagnum = tag[0], taginfo = tag[1]; + + var subfields = {}; + + $.each( taginfo.subfields, function( i, subfield ) { + subfields[ subfield[0] ] = subfield[1]; + } ); + + _framework_mappings[frameworkcode][tagnum] = $.extend( {}, taginfo, { subfields: subfields } ); + } ); + } + + _importFramework( '', defaultFramework.framework ); + + var KohaBackend = { + NOT_EMPTY: {}, // Sentinel value + + GetAllTagsInfo: function( frameworkcode, tagnumber ) { + return _framework_mappings[frameworkcode]; + }, + + GetAuthorisedValues: function( category ) { + return _authorised_values[category]; + }, + + GetTagInfo: function( frameworkcode, tagnumber ) { + if ( !_framework_mappings[frameworkcode] ) return undefined; + return _framework_mappings[frameworkcode][tagnumber]; + }, + + GetRecord: function( id, callback ) { + $.get( + '/cgi-bin/koha/svc/bib/' + id + ).done( function( data ) { + var record = new MARC.Record(); + record.loadMARCXML(data); + callback(record); + } ).fail( function( data ) { + callback( { data: { error: data } } ); + } ); + }, + + CreateRecord: function( record, callback ) { + $.ajax( { + type: 'POST', + url: '/cgi-bin/koha/svc/new_bib', + data: record.toXML(), + contentType: 'text/xml' + } ).done( function( data ) { + callback( _fromXMLStruct( data ) ); + } ).fail( function( data ) { + callback( { error: data } ); + } ); + }, + + SaveRecord: function( id, record, callback ) { + $.ajax( { + type: 'POST', + url: '/cgi-bin/koha/svc/bib/' + id, + data: record.toXML(), + contentType: 'text/xml' + } ).done( function( data ) { + callback( _fromXMLStruct( data ) ); + } ).fail( function( data ) { + callback( { data: { error: data } } ); + } ); + }, + + GetTagsBy: function( frameworkcode, field, value ) { + var result = {}; + + $.each( _frameworks[frameworkcode], function( undef, tag ) { + var tagnum = tag[0], taginfo = tag[1]; + + if ( taginfo[field] == value ) result[tagnum] = true; + } ); + + return result; + }, + + GetSubfieldsBy: function( frameworkcode, field, value ) { + var result = {}; + + $.each( _frameworks[frameworkcode], function( undef, tag ) { + var tagnum = tag[0], taginfo = tag[1]; + + $.each( taginfo.subfields, function( undef, subfield ) { + var subfieldcode = subfield[0], subfieldinfo = subfield[1]; + + if ( subfieldinfo[field] == value ) { + if ( !result[tagnum] ) result[tagnum] = {}; + + result[tagnum][subfieldcode] = true; + } + } ); + } ); + + return result; + }, + + FillRecord: function( frameworkcode, record, allTags ) { + $.each( _frameworks[frameworkcode], function( undef, tag ) { + var tagnum = tag[0], taginfo = tag[1]; + + if ( taginfo.mandatory != "1" && !allTags ) return; + + var fields = record.fields(tagnum); + + if ( fields.length == 0 ) { + var newField = new MARC.Field( tagnum, ' ', ' ', [] ); + fields.push( newField ); + record.addFieldGrouped( newField ); + + if ( tagnum < '010' ) { + newField.addSubfield( [ '@', '' ] ); + return; + } + } + + $.each( taginfo.subfields, function( undef, subfield ) { + var subfieldcode = subfield[0], subfieldinfo = subfield[1]; + + if ( subfieldinfo.mandatory != "1" && !allTags ) return; + + $.each( fields, function( undef, field ) { + if ( !field.hasSubfield(subfieldcode) ) field.addSubfieldGrouped( [ subfieldcode, '' ] ); + } ); + } ); + } ); + }, + + ValidateRecord: function( frameworkcode, record ) { + var errors = []; + + var mandatoryTags = KohaBackend.GetTagsBy( '', 'mandatory', '1' ); + var mandatorySubfields = KohaBackend.GetSubfieldsBy( '', 'mandatory', '1' ); + var nonRepeatableTags = KohaBackend.GetTagsBy( '', 'repeatable', '0' ); + var nonRepeatableSubfields = KohaBackend.GetSubfieldsBy( '', 'repeatable', '0' ); + + $.each( mandatoryTags, function( tag ) { + if ( !record.hasField( tag ) ) errors.push( { type: 'missingTag', tag: tag } ); + } ); + + var seenTags = {}; + + $.each( record.fields(), function( undef, field ) { + if ( seenTags[ field.tagnumber() ] && nonRepeatableTags[ field.tagnumber() ] ) { + errors.push( { type: 'unrepeatableTag', line: field.sourceLine, tag: field.tagnumber() } ); + return; + } + + seenTags[ field.tagnumber() ] = true; + + var seenSubfields = {}; + + $.each( field.subfields(), function( undef, subfield ) { + if ( seenSubfields[ subfield[0] ] != null && nonRepeatableSubfields[ field.tagnumber() ][ subfield[0] ] ) { + errors.push( { type: 'unrepeatableSubfield', subfield: subfield[0], line: field.sourceLine } ); + } else { + seenSubfields[ subfield[0] ] = subfield[1]; + } + } ); + + $.each( mandatorySubfields[ field.tagnumber() ] || {}, function( subfield ) { + if ( !seenSubfields[ subfield ] ) { + errors.push( { type: 'missingSubfield', subfield: subfield[0], line: field.sourceLine } ); + } + } ); + } ); + + return errors; + }, + }; + + return KohaBackend; +} ); diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/macros.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/macros.js new file mode 100644 index 0000000..8aaa2f3 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/macros.js @@ -0,0 +1,196 @@ +define( [ 'widget' ], function( Widget ) { + function _setIndicators( editor, ind1, ind2 ) { + var info = editor.getLineInfo( editor.cm.getCursor() ); + if (!info || !info.subfields) return false; + + var cur = editor.cm.getCursor(); + + var indicators = [ ind1 || info.contents.substring(4, 5) || '_', ind2 || info.contents.substring(6, 7) || '_' ]; + + editor.cm.replaceRange( + info.tagNumber + ' ' + indicators.join(' ') + ' ' + info.contents.substring(8), + { line: cur.line, ch: 0 }, + { line: cur.line }, + 'marcAware' + ); + + return true; + } + + var _commandGenerators = [ + [ /^copy field data$/i, function() { + return function( editor, state ) { + var info = editor.getLineInfo( editor.cm.getCursor() ); + if (!info) return false; + + if (info.subfields) { + state.clipboard = info.contents.substring(4); + } else { + state.clipboard = info.contents.substring(8); + } + }; + } ], + [ /^copy subfield data$/i, function() { + return function( editor, state ) { + var info = editor.getLineInfo( editor.cm.getCursor() ); + if (!info) return false; + + var cur = editor.cm.getCursor(); + + if (info.subfields) { + for (var i = 0; i < info.subfields.length; i++) { + var end = i == info.subfields.length - 1 ? info.contents.length : info.subfields[i+1].ch; + if (cur.ch > end) continue; + + state.clipboard = info.contents.substring(info.subfields[i].ch + 3, end); + return; + } + } + + return false; + } + } ], + [ /^delete field$/i, function() { + return function( editor, state ) { + var cur = editor.cm.getCursor(); + + editor.cm.replaceRange( "", { line: cur.line, ch: 0 }, { line: cur.line + 1, ch: 0 }, 'marcAware' ); + } + } ], + [ /^goto field end$/i, function() { + return function( editor, state ) { + editor.cm.setCursor( { line: editor.cm.lastLine() } ); + } + } ], + [ /^goto field (\w{3})$/i, function(field) { + var matcher = new RegExp('^' + field + ' '); + return function( editor, state ) { + for ( var line = 0, contents; (contents = editor.cm.getLine(line)); line++ ) { + if ( matcher.exec( contents ) ) { + editor.cm.setCursor( { line: line, ch: 0 } ); + return; + } + } + + return false; + } + } ], + [ /^goto subfield end$/i, function() { + return function( editor, state ) { + var cur = editor.cm.getCursor(); + + editor.cm.setCursor( { line: cur.line } ); + } + } ], + [ /^goto subfield (\w)$/i, function( code ) { + return function( editor, state ) { + var info = editor.getLineInfo( editor.cm.getCursor() ); + if (!info || !info.subfields) return false; + + var cur = editor.cm.getCursor(); + + for (var i = 0; i < info.subfields.length; i++) { + if ( info.subfields[i].code != code ) continue; + + var end = i == info.subfields.length - 1 ? info.contents.length : info.subfields[i+1].ch; + editor.cm.setCursor( { line: cur.line, ch: end } ); + return; + } + + return false; + } + } ], + [ /^insert (new )?field (\w{3}) data=(.*)/i, function(undef, field, data) { + var new_contents = field + ( field < '100' ? ' ' : ' _ _ ' ) + data.replace(/\\([0-9a-z])/g, '$$$1 '); + return function( editor, state ) { + var line, contents; + + for ( line = 0; (contents = editor.cm.getLine(line)); line++ ) { + if ( contents && contents[0] > field[0] ) break; + } + + if ( line > editor.cm.lastLine() ) { + new_contents = '\n' + new_contents; + } else { + new_contents = new_contents + '\n'; + } + + editor.cm.replaceRange( new_contents, { line: line, ch: 0 }, null, 'marcAware' ); + editor.cm.setCursor( { line: line, ch: 0 } ); + } + } ], + [ /^insert (new )?subfield (\w) data=(.*)/i, function(undef, subfield, data) { + return function( editor, state ) { + editor.cm.replaceRange( '$' + subfield + ' ' + data, { line: editor.cm.getCursor().line }, null, 'marcAware' ); + } + } ], + [ /^paste$/i, function() { + return function( editor, state ) { + var cur = editor.cm.getCursor(); + + editor.cm.replaceRange( state.clipboard, cur, null, 'marcAware' ); + } + } ], + [ /^set indicator([12])=([ _0-9])$/i, function( ind, value ) { + return function( editor, state ) { + return ind == '1' ? _setIndicators( editor, ind1, null ) : _setIndicators( editor, null, ind2 ); + } + } ], + [ /^set indicators=([ _0-9])([ _0-9])$/i, function( ind1, ind2 ) { + return function( editor, state ) { + return _setIndicators( editor, ind1, ind2 ); + } + } ], + ]; + + var Macros = { + Compile: function( macro ) { + var result = { commands: [], errors: [] }; + + $.each( macro.split(/\r\n|\n/), function( line, contents ) { + var command; + + if ( contents.match(/^\s*$/) ) return; + + $.each( _commandGenerators, function( undef, gen ) { + var match; + + if ( !( match = gen[0].exec( contents ) ) ) return; + + command = gen[1].apply(null, match.slice(1)); + return false; + } ); + + if ( !command ) { + result.errors.push( { line: line, error: 'unrecognized' } ); + } + + result.commands.push( { func: command, orig: contents, line: line } ); + } ); + + return result; + }, + Run: function( editor, macro ) { + var compiled = Macros.Compile(macro); + if ( compiled.errors.length ) return { errors: compiled.errors }; + var state = { + clipboard: '', + }; + + var result = { errors: [] }; + + editor.cm.operation( function() { + $.each( compiled.commands, function( undef, command ) { + if ( command.func( editor, state ) === false ) { + result.errors.push( { line: command.line, error: 'failed' } ); + return false; + } + } ); + } ); + + return result; + }, + }; + + return Macros; +} ); diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-editor.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-editor.js new file mode 100644 index 0000000..3c6992b --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-editor.js @@ -0,0 +1,358 @@ +define( [ 'marc-record', 'koha-backend', 'preferences', 'text-marc', 'widget' ], function( MARC, KohaBackend, Preferences, TextMARC, Widget ) { + var NOTIFY_TIMEOUT = 250; + + function editorCursorActivity( cm ) { + var editor = cm.marceditor; + if ( editor.textMode ) return; + + $('#status-tag-info').empty(); + $('#status-subfield-info').empty(); + + var info = editor.getLineInfo( cm.getCursor() ); + + if ( !info ) return; // No tag at all on this line + + var taginfo = KohaBackend.GetTagInfo( '', info.tagNumber ); + $('#status-tag-info').html( '' + info.tagNumber + ': ' ); + + if ( taginfo ) { + $('#status-tag-info').append( taginfo.lib ); + + if ( !info.currentSubfield ) return; // No current subfield + + var subfieldinfo = taginfo.subfields[info.currentSubfield]; + $('#status-subfield-info').html( '$' + info.currentSubfield + ': ' ); + + if ( subfieldinfo ) { + $('#status-subfield-info').append( subfieldinfo.lib ); + } else { + $('#status-subfield-info').append( '' + _("Unknown subfield") + '' ); + } + } else { + $('#status-tag-info').append( '' + _("Unknown tag") + '' ); + } + } + + function editorBeforeChange( cm, change ) { + var editor = cm.marceditor; + if ( editor.textMode || change.origin == 'marcAware' ) return; + + // FIXME: Should only cancel changes if this is a control field/subfield widget + if ( change.from.line !== change.to.line || Math.abs( change.from.ch - change.to.ch ) > 1 || change.text.length != 1 || change.text[0].length != 0 ) return; // Not single-char change + + if ( change.from.ch == change.to.ch - 1 && cm.findMarksAt( { line: change.from.line, ch: change.from.ch + 1 } ).length ) { + change.cancel(); + } else if ( change.from.ch == change.to.ch && cm.findMarksAt(change.from).length && !change.text[0].match(/^[$|ǂ‡]$/) ) { + change.cancel(); + } + } + + function editorChange( cm, change ) { + var editor = cm.marceditor; + if ( editor.textMode ) return; + + var updatedLines = {}; + do { + var from = change.from; + var to = change.to; + if ( to.line < from.line || to.ch < from.ch ) { + var temp = from; + from = to; + to = temp; + } + + var startLine, endLine; + if ( change.text.length == 2 && from.line == to.line && from.ch == to.ch) { + if ( from.ch == 0 ) { + startLine = endLine = from.line; + } else if ( from.ch == cm.getLine(from.line).length ){ + startLine = endLine = from.line + 1; + } + } else { + startLine = (from.ch == cm.getLine(from.line).length && from.line < to.line) ? Math.min(cm.lastLine(), from.line + 1) : from.line; + endLine = ((to.ch == 0 && from.line < to.line) ? Math.max(to.line - 1, 0) : to.line) + change.text.length - 1; + } + + for ( var line = startLine; line <= endLine; line++ ) { + if ( updatedLines[line] ) continue; + + if ( Preferences.user.fieldWidgets ) Widget.UpdateLine( cm.marceditor, line ); + if ( change.origin != 'setValue' && change.origin != 'marcWidgetPrefill' ) cm.addLineClass( line, 'wrapper', 'modified-line' ); + updatedLines[line] = true; + } + } while ( change = change.next ) + + Widget.ActivateAt( cm, cm.getCursor() ); + cm.marceditor.startNotify(); + } + + // Editor helper functions + function activateTabPosition( cm, cur, idx ) { + cm.setCursor( cur ); + Widget.ActivateAt( cm, cur, idx ); + } + + function getTabPositions( editor, cur ) { + var info = editor.getLineInfo( cur || editor.cm.getCursor() ); + + if ( info ) { + if ( info.subfields ) { + var positions = [ 0, 4, 6 ]; + + $.each( info.subfields, function( undef, subfield ) { + positions.push( subfield.ch + 3 ); + } ); + + return positions; + } else { + return [ 0, 4 ]; + } + } else { + return []; + } + } + + var _editorKeys = { + Enter: function( cm ) { + var cursor = cm.getCursor(); + cm.replaceRange( '\n', { line: cursor.line }, null, 'marcAware' ); + cm.setCursor( { line: cursor.line + 1, ch: 0 } ); + }, + + 'Shift-Enter': function( cm ) { + var cursor = cm.getCursor(); + cm.replaceRange( '\n', { line: cursor.line, ch: 0 }, null, 'marcAware' ); + cm.setCursor( { line: cursor.line, ch: 0 } ); + }, + + 'Ctrl-X': function( cm ) { + // Delete subfield (or cut) + if ( cm.somethingSelected() ) return true; + + var cur = cm.getCursor(); + var info = cm.marceditor.getLineInfo( cur ); + if ( !info || !info.subfields ) return true; + + for (var i = 0; i < info.subfields.length; i++) { + var end = i == info.subfields.length - 1 ? info.contents.length : info.subfields[i+1].ch; + if (cur.ch > end) continue; + + cm.replaceRange( "", { line: cur.line, ch: info.subfields[i].ch }, { line: cur.line, ch: end }, 'marcAware' ); + return; + } + }, + + 'Shift-Ctrl-X': function( cm ) { + // Delete line + var cur = cm.getCursor(); + + cm.replaceRange( "", { line: cur.line, ch: 0 }, { line: cur.line + 1, ch: 0 }, 'marcAware' ); + }, + + Tab: function( cm ) { + // Move through parts of tag/fixed fields + var positions = getTabPositions( cm.marceditor ); + var cur = cm.getCursor(); + + for ( var i = 0; i < positions.length; i++ ) { + if ( positions[i] > cur.ch ) { + activateTabPosition( cm, { line: cur.line, ch: positions[i] } ); + return false; + } + } + + cm.setCursor( { line: cur.line + 1, ch: 0 } ); + }, + + 'Shift-Tab': function( cm ) { + // Move backwards through parts of tag/fixed fields + var positions = getTabPositions( cm.marceditor ); + var cur = cm.getCursor(); + + for ( var i = positions.length - 1; i >= 0; i-- ) { + if ( positions[i] < cur.ch ) { + activateTabPosition( cm, { line: cur.line, ch: positions[i] } ); + return false; + } + } + + if ( cur.line == 0 ) return; + + var prevPositions = getTabPositions( cm, { line: cur.line - 1, ch: cm.getLine( cur.line - 1 ).length } ); + + if ( prevPositions.length ) { + activateTabPosition( cm, { line: cur.line - 1, ch: prevPositions[ prevPositions.length - 1 ] }, -1 ); + } else { + cm.setCursor( { line: cur.line - 1, ch: 0 } ); + } + }, + }; + + function MARCEditor( position ) { + this.cm = CodeMirror( + position, + { + extraKeys: _editorKeys, + gutters: [ + 'modified-line-gutter', + ], + lineWrapping: true, + mode: { + name: 'marc', + nonRepeatableTags: KohaBackend.GetTagsBy( '', 'repeatable', '0' ), + nonRepeatableSubfields: KohaBackend.GetSubfieldsBy( '', 'repeatable', '0' ) + } + } + ); + this.cm.marceditor = this; + + this.cm.on( 'beforeChange', editorBeforeChange ); + this.cm.on( 'change', editorChange ); + this.cm.on( 'cursorActivity', editorCursorActivity ); + + this.subscribers = []; + this.subscribe( function( marceditor ) { + Widget.Notify( marceditor ); + } ); + } + + MARCEditor.prototype.setUseWidgets = function( val ) { + if ( val ) { + for ( var line = 0; line <= this.cm.lastLine(); line++ ) { + Widget.UpdateLine( this, line ); + } + } else { + $.each( this.cm.getAllMarks(), function( undef, mark ) { + if ( mark.widget ) mark.widget.clearToText(); + } ); + } + }; + + MARCEditor.prototype.focus = function() { + this.cm.focus(); + }; + + MARCEditor.prototype.refresh = function() { + this.cm.refresh(); + }; + + MARCEditor.prototype.displayRecord = function( record ) { + this.cm.setValue( TextMARC.RecordToText(record) ); + }; + + MARCEditor.prototype.getRecord = function() { + this.textMode = true; + + $.each( this.cm.getAllMarks(), function( undef, mark ) { + if ( mark.widget ) mark.widget.clearToText(); + } ); + var record = TextMARC.TextToRecord( this.cm.getValue() ); + for ( var line = 0; line <= this.cm.lastLine(); line++ ) { + if ( Preferences.user.fieldWidgets ) Widget.UpdateLine( this, line ); + } + + this.textMode = false; + + return record; + }; + + MARCEditor.prototype.getLineInfo = function( pos ) { + var contents = this.cm.getLine( pos.line ); + if ( contents == null ) return {}; + + var tagNumber = contents.match( /^([A-Za-z0-9]{3}) / ); + + if ( !tagNumber ) return null; // No tag at all on this line + tagNumber = tagNumber[1]; + + if ( tagNumber < '010' ) return { tagNumber: tagNumber, contents: contents }; // No current subfield + + var matcher = /[$|ǂ‡]([a-z0-9%]) /g; + var match; + + var subfields = []; + var currentSubfield; + + while ( ( match = matcher.exec(contents) ) ) { + subfields.push( { code: match[1], ch: match.index } ); + if ( match.index < pos.ch ) currentSubfield = match[1]; + } + + return { tagNumber: tagNumber, subfields: subfields, currentSubfield: currentSubfield, contents: contents }; + }; + + MARCEditor.prototype.addError = function( line, error ) { + var found = false; + var options = {}; + + if ( line == null ) { + line = 0; + options.above = true; + } + + $.each( this.cm.getLineHandle(line).widgets || [], function( undef, widget ) { + if ( !widget.isErrorMarker ) return; + + found = true; + + $( widget.node ).append( '; ' + error ); + widget.changed(); + + return false; + } ); + + if ( found ) return; + + var node = $( '
    ' + error + '
    ' )[0]; + var widget = this.cm.addLineWidget( line, node, options ); + + widget.node = node; + widget.isErrorMarker = true; + }; + + MARCEditor.prototype.removeErrors = function() { + for ( var line = 0; line < this.cm.lineCount(); line++ ) { + $.each( this.cm.getLineHandle( line ).widgets || [], function( undef, lineWidget ) { + if ( lineWidget.isErrorMarker ) lineWidget.clear(); + } ); + } + }; + + MARCEditor.prototype.getFixedField = function(field) { + field += ' '; + for ( var line = 0; line < this.cm.lineCount(); line++ ) { + var contents = this.cm.getLine(line); + if ( contents.substr( 0, 4 ) != field ) continue; + + var marks = this.cm.findMarksAt( { line: line, ch: 4 } ); + if ( marks[0] && marks[0].widget ) { + return marks[0].widget.text; + } else { + return contents.substr(4); + } + } + + return null; + }; + + MARCEditor.prototype.startNotify = function() { + if ( this.notifyTimeout ) clearTimeout( this.notifyTimeout ); + this.notifyTimeout = setTimeout( $.proxy( function() { + this.notifyAll(); + + this.notifyTimeout = null; + }, this ), NOTIFY_TIMEOUT ); + }; + + MARCEditor.prototype.notifyAll = function() { + $.each( this.subscribers, $.proxy( function( undef, subscriber ) { + subscriber(this); + }, this ) ); + }; + + MARCEditor.prototype.subscribe = function( subscriber ) { + this.subscribers.push( subscriber ); + }; + + return MARCEditor; +} ); diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-mode.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-mode.js new file mode 100644 index 0000000..bfa0174 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-mode.js @@ -0,0 +1,129 @@ +/** + * Textual MARC mode for CodeMirror. + * Copyright (c) 2013 ByWater + */ + +// Expected format: 245 _ 1 $a Pizza |c 34ars + +CodeMirror.defineMode( 'marc', function( config, modeConfig ) { + modeConfig.nonRepeatableTags = modeConfig.nonRepeatableTags || {}; + modeConfig.nonRepeatableSubfields = modeConfig.nonRepeatableSubfields || {}; + + return { + startState: function( prevState ) { + var state = prevState || {}; + + if ( !prevState ) { + state.seenTags = {}; + } + + state.indicatorNeeded = false; + state.subAllowed = true; + state.subfieldCode = undefined; + state.tagNumber = undefined; + state.seenSubfields = {}; + + return state; + }, + copyState: function( prevState ) { + var result = $.extend( {}, prevState ); + result.seenTags = $.extend( {}, prevState.seenTags ); + result.seenSubfields = $.extend( {}, prevState.seenSubfields ); + + return result; + }, + token: function( stream, state ) { + var match; + if ( stream.sol() ) { + this.startState( state ); + if ( match = stream.match( /[0-9A-Za-z]+/ ) ) { + match = match[0]; + if ( match.length != 3 ) { + if ( stream.eol() && match.length < 3 ) { + // Don't show error for incomplete number + return 'tagnumber'; + } else { + stream.skipToEnd(); + return 'error'; + } + } + + state.tagNumber = match; + if ( state.tagNumber < '010' ) { + // Control field + state.subAllowed = false; + } + + if ( state.seenTags[state.tagNumber] && modeConfig.nonRepeatableTags[state.tagNumber] ) { + return 'bad-tagnumber'; + } else { + state.seenTags[state.tagNumber] = true; + return 'tagnumber'; + } + } else { + stream.skipToEnd(); + return 'error'; + } + } + + if ( stream.eol() ) { + return; + } + + if ( !state.subAllowed && stream.pos == 3 ) { + if ( stream.next() == ' ' ) { + return 'reqspace'; + } else { + stream.skipToEnd(); + return 'error'; + } + } + + if ( stream.pos < 8 && state.subAllowed ) { + switch ( stream.pos ) { + case 3: + case 5: + case 7: + if ( stream.next() == ' ' ) { + return 'reqspace'; + } else { + stream.skipToEnd(); + return 'error'; + } + case 4: + case 6: + if ( /[0-9A-Za-z_]/.test( stream.next() ) ) { + return 'indicator'; + } else { + stream.skipToEnd(); + return 'error'; + } + } + } + + if ( state.subAllowed ) { + if ( stream.pos != 8 && stream.match( /[^$|ǂ‡]+/ ) ) return; + + if ( stream.eat( /[$|ǂ‡]/ ) ) { + var subfieldCode; + if ( ( subfieldCode = stream.eat( /[a-z0-9%]/ ) ) && stream.eat( ' ' ) ) { + state.subfieldCode = subfieldCode; + if ( state.seenSubfields[state.subfieldCode] && ( modeConfig.nonRepeatableSubfields[state.tagNumber] || {} )[state.subfieldCode] ) { + return 'bad-subfieldcode'; + } else { + state.seenSubfields[state.subfieldCode] = true; + return 'subfieldcode'; + } + } + } + + if ( stream.pos < 11 && ( !stream.eol() || stream.pos == 8 ) ) { + stream.skipToEnd(); + return 'error'; + } + } else { + stream.skipToEnd(); + } + } + }; +} ); diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-record.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-record.js new file mode 100644 index 0000000..95adf85 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-record.js @@ -0,0 +1,351 @@ +/** + * Adapted and cleaned up from biblios.net, which is purportedly under the GPL. + * Source: http://git.librarypolice.com/?p=biblios.git;a=blob_plain;f=plugins/marc21editor/marcrecord.js;hb=master + * + * ISO2709 import/export is cribbed from marcjs, which is under the MIT license. + * Source: https://github.com/fredericd/marcjs/blob/master/lib/marcjs.js + */ + +define( function() { + var MARC = {}; + + var _escape_map = { + "<": "<", + "&": "&", + "\"": """ + }; + + function _escape(str) { + return str.replace( /[<&"]/, function (c) { return _escape_map[c] } ); + } + + function _intpadded(i, digits) { + i = i + ''; + while (i.length < digits) { + i = '0' + i; + } + return i; + } + + MARC.Record = function (fieldlist) { + this._fieldlist = fieldlist || []; + } + + $.extend( MARC.Record.prototype, { + leader: function(val) { + var field = this.field('000'); + + if (val) { + if (field) { + field.subfield( '@', val ); + } else { + field = new MARC.Field( '000', '', '', [ [ '@', val ] ] ); + this.addFieldGrouped(field); + } + } else { + return ( field && field.subfield('@') ) || ' nam a22 7a 4500'; + } + }, + + /** + * If a tagnumber is given, returns all fields with that tagnumber. + * Otherwise, returns all fields. + */ + fields: function(fieldno) { + if (!fieldno) return this._fieldlist; + + var results = []; + for(var i=0; i= 0; i-- ) { + if ( this._fieldlist[i].tagnumber()[0] <= field.tagnumber()[0] ) { + this._fieldlist.splice(i+1, 0, field); + return true; + } + } + this._fieldlist.push(field); + return true; + }, + + /** + * Removes the first field with the given tagnumber. Returns false if no + * such field was found. + */ + removeField: function(fieldno) { + for(var i=0; i= 0; i-- ) { + if ( i == 0 && _kind( sf[0] ) < _kind( this._subfields[i][0] ) ) { + this._subfields.splice( 0, 0, sf ); + return true; + } else if ( _kind( this._subfields[i][0] ) <= _kind( sf[0] ) ) { + this._subfields.splice( i + 1, 0, sf ); + return true; + } + } + + this._subfields.push(sf); + return true; + }, + + subfield: function(code, val) { + var sf = ''; + for(var i = 0; i' + _escape( this._subfields[0][1] ) + ''; + } else if ( this._tagnumber < '010' ) { + return '' + _escape( this._subfields[0][1] ) + ''; + } else { + var result = ''; + for( var i = 0; i< this._subfields.length; i++) { + result += ''; + result += _escape( this._subfields[i][1] ); + result += ''; + } + result += ''; + + return result; + } + } + } ); + + return MARC; +} ); diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js new file mode 100644 index 0000000..1372dbf --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js @@ -0,0 +1,28 @@ +define( function() { + var Preferences = { + Load: function( borrowernumber ) { + if ( !borrowernumber ) return; + var saved_prefs; + try { + saved_prefs = JSON.parse( localStorage[ 'cateditor_preferences_' + borrowernumber ] ); + } catch (e) {} + + Preferences.user = $.extend( { + // Preference defaults + fieldWidgets: true, + font: 'monospace', + fontSize: '1em', + macros: {}, + }, saved_prefs ); + }, + + Save: function( borrowernumber ) { + if ( !borrowernumber ) return; + if ( !Preferences.user ) Preferences.Load(borrowernumber); + + localStorage[ 'cateditor_preferences_' + borrowernumber ] = JSON.stringify(Preferences.user); + }, + }; + + return Preferences; +} ); diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/resources.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/resources.js new file mode 100644 index 0000000..24806ce --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/resources.js @@ -0,0 +1,19 @@ +define( [ 'module' ], function( module ) { + var _allResources = []; + + var Resources = { + GetAll: function() { + return $.when.call( null, _allResources ); + } + }; + + function _res( name, deferred ) { + Resources[name] = deferred; + _allResources.push(deferred); + } + + _res( 'marc21/xml/006', $.get( module.config().themelang + '/data/marc21_field_006.xml' ) ); + _res( 'marc21/xml/008', $.get( module.config().themelang + '/data/marc21_field_008.xml' ) ); + + return Resources; +} ); diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/search.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/search.js new file mode 100644 index 0000000..916076c --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/search.js @@ -0,0 +1,96 @@ +define( [ 'marc-record', 'pz2' ], function( MARC, Pazpar2 ) { + //var _pz; + var _onresults; + var _recordCache = {}; + var _options; + + var Search = { + Init: function( targets, options ) { + var initOpts = {}; + + $.each( targets, function ( url, info ) { + initOpts[ 'pz:name[' + url + ']' ] = info.name; + initOpts[ 'pz:queryencoding[' + url + ']' ] = info.encoding; + initOpts[ 'pz:xslt[' + url + ']' ] = info.kohasyntax.toLowerCase() + '-work-groups.xsl'; + initOpts[ 'pz:requestsyntax[' + url + ']' ] = info.syntax; + + // Load in default CCL mappings + // Pazpar2 seems to have a bug where wildcard cclmaps are ignored. + // What an incredible surprise. + initOpts[ 'pz:cclmap:term[' + url + ']' ] = 'u=1016 t=l,r s=al'; + initOpts[ 'pz:cclmap:Author-name[' + url + ']' ] = 'u=1004 s=al'; + initOpts[ 'pz:cclmap:Classification-Dewey[' + url + ']' ] = 'u=13'; + initOpts[ 'pz:cclmap:Classification-LC[' + url + ']' ] = 'u=16'; + initOpts[ 'pz:cclmap:Date[' + url + ']' ] = 'u=30 r=r'; + initOpts[ 'pz:cclmap:Identifier-ISBN[' + url + ']' ] = 'u=7'; + initOpts[ 'pz:cclmap:Identifier-ISSN[' + url + ']' ] = 'u=8'; + initOpts[ 'pz:cclmap:Identifier-publisher-for-music[' + url + ']' ] = 'u=51'; + initOpts[ 'pz:cclmap:Identifier-standard[' + url + ']' ] = 'u=1007'; + initOpts[ 'pz:cclmap:LC-card-number[' + url + ']' ] = 'u=9'; + initOpts[ 'pz:cclmap:Local-number[' + url + ']' ] = 'u=12'; + initOpts[ 'pz:cclmap:Subject[' + url + ']' ] = 'u=21 s=al'; + initOpts[ 'pz:cclmap:Title[' + url + ']' ] = 'u=4 s=al'; + + if ( info.authentication ) initOpts[ 'pz:authentication[' + url + ']' ] = info.authentication; + } ); + + _options = $.extend( { + initopts: initOpts, + onshow: Search._onshow, + errorhandler: Search._onerror, + }, options ); + + _pz = new Pazpar2( _options ); + }, + Reconnect: function() { + _pz.reset(); + _pz = new Pazpar2( _options ); + }, + Start: function( targets, q, limit ) { + Search.includedTargets = []; + recordcache = {}; + + $.each( targets, function ( url, info ) { + if ( !info.disabled ) Search.includedTargets.push( url ); + } ); + + _pz.search( q, limit, 'relevance:0', 'pz:id=' + Search.includedTargets.join( '|' ) ); + return true; + }, + Fetch: function( offset ) { + _pz.show( offset ); + }, + GetDetailedRecord: function( recid, callback ) { + if ( _recordCache[recid] ) { + callback( _recordCache[recid] ); + return; + } + + _pz.record( recid, 0, undefined, { callback: function(data) { + var record = _recordCache[recid] = new MARC.Record(); + record.loadMARCXML(data.xmlDoc); + + callback(record); + } } ); + }, + IsAvailable: function() { + return _pz.initStatusOK; + }, + _onshow: function( data ) { + $.each( data.hits, function( undef, hit ) { + hit.id = 'search:' + encodeURIComponent( hit.recid[0] ); + } ); + + _options.onresults( data ); + }, + _onerror: function( error ) { + if ( _options.oniniterror && !_pz.initStatusOK ) { + _options.oniniterror( error ); + } else { + _options.onerror( error ); + } + } + }; + + return Search; +} ); diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/text-marc.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/text-marc.js new file mode 100644 index 0000000..5c76c62 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/text-marc.js @@ -0,0 +1,78 @@ +define( [ 'marc-record' ], function( MARC ) { + return { + RecordToText: function( record ) { + var lines = []; + var fields = record.fields(); + + for ( var i = 0; i < fields.length; i++ ) { + var field = fields[i]; + + if ( field.isControlField() ) { + lines.push( field.tagnumber() + ' ' + field.subfield( '@' ) ); + } else { + var result = [ field.tagnumber() + ' ' ]; + + result.push( field.indicator(0) == ' ' ? '_' : field.indicator(0), ' ' ); + result.push( field.indicator(1) == ' ' ? '_' : field.indicator(1), ' ' ); + + $.each( field.subfields(), function( i, subfield ) { + result.push( '$' + subfield[0] + ' ' + subfield[1] ); + } ); + + lines.push( result.join('') ); + } + } + + return lines.join('\n'); + }, + + TextToRecord: function( text ) { + var record = new MARC.Record(); + var errors = []; + + $.each( text.split('\n'), function( i, line ) { + var tagNumber = line.match( /^([A-Za-z0-9]{3}) / ); + + if ( !tagNumber ) { + errors.push( { type: 'noTag', line: i } ); + return; + } + tagNumber = tagNumber[1]; + + if ( tagNumber < '010' ) { + var field = new MARC.Field( tagNumber, ' ', ' ', [ [ '@', line.substring( 4 ) ] ] ); + field.sourceLine = i; + record.addField( field ); + } else { + var indicators = line.match( /^... ([0-9A-Za-z_]) ([0-9A-Za-z_])/ ); + if ( !indicators ) { + errors.push( { type: 'noIndicators', line: i } ); + return; + } + + var field = new MARC.Field( tagNumber, ( indicators[1] == '_' ? ' ' : indicators[1] ), ( indicators[2] == '_' ? ' ' : indicators[2] ), [] ); + + var matcher = /[$|ǂ‡]([a-z0-9%]) /g; + var match; + + var subfields = []; + + while ( ( match = matcher.exec(line) ) ) { + subfields.push( { code: match[1], ch: match.index } ); + } + + $.each( subfields, function( i, subfield ) { + var next = subfields[ i + 1 ]; + + field.addSubfield( [ subfield.code, line.substring( subfield.ch + 3, next ? next.ch : line.length ) ] ); + } ); + + field.sourceLine = i; + record.addField( field ); + } + } ); + + return errors.length ? { errors: errors } : record; + } + }; +} ); diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/widget.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/widget.js new file mode 100644 index 0000000..5e7bb48 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/widget.js @@ -0,0 +1,287 @@ +define( [ 'resources' ], function( Resources ) { + var _widgets = {}; + + var Widget = { + Register: function( tagfield, widget ) { + _widgets[tagfield] = widget; + }, + + PadNum: function( number, length ) { + var result = number.toString(); + + while ( result.length < length ) result = '0' + result; + + return result; + }, + + PadString: function( result, length ) { + while ( result.length < length ) result = ' ' + result; + + return result; + }, + + PadStringRight: function( result, length ) { + result = '' + result; + while ( result.length < length ) result += ' '; + + return result; + }, + + Base: { + // Marker utils + clearToText: function() { + var range = this.mark.find(); + if ( this.text == null ) throw new Error('Tried to clear widget with no text'); + this.mark.doc.replaceRange( this.text, range.from, range.to, 'marcAware' ); + }, + + reCreate: function() { + this.postCreate( this.node, this.mark ); + }, + + // Fixed field utils + bindFixed: function( sel, start, end ) { + var $node = $( this.node ).find( sel ); + $node.val( this.getFixed( start, end ) ); + + var widget = this; + var $collapsed = $( '' + $node.val() + '' ).insertAfter( $node ); + + function show() { + $collapsed.hide(); + $node.val( widget.getFixed( start, end ).replace(/\s+$/, '') ); + $node.show(); + $node[0].focus(); + } + + function hide() { + $node.hide(); + $collapsed.text( Widget.PadStringRight( $node.val(), end - start ) ).show(); + } + + $node.on( 'change keyup', function() { + widget.setFixed( start, end, $node.val(), '+input' ); + } ).focus( show ).blur( hide ); + + hide(); + + $collapsed.click( show ); + }, + + getFixed: function( start, end ) { + return this.text.substring( start, end ); + }, + + setFixed: function( start, end, value, source ) { + this.setText( this.text.substring( 0, start ) + Widget.PadStringRight( value.toString().substr( 0, end - start ), end - start ) + this.text.substring( end ), source ); + }, + + setText: function( text, source ) { + if ( source == '+input' ) this.mark.doc.cm.addLineClass( this.mark.find().from.line, 'wrapper', 'modified-line' ); + this.text = text; + this.editor.startNotify(); + }, + + createFromXML: function( resourceId ) { + var widget = this; + + Resources[resourceId].done( function( xml ) { + $(widget.node).find('.widget-loading').remove(); + var $matSelect = $('').appendTo(widget.node); + var $contents = $('').appendTo(widget.node); + var materialInfo = {}; + + $('Tagfield', xml).children('Material').each( function() { + $matSelect.append( '' ); + + materialInfo[ $(this).attr('id') ] = this; + } ); + + $matSelect.change( function() { + widget.loadXMLMaterial( materialInfo[ $matSelect.val() ] ); + } ).change(); + } ); + }, + + loadXMLMaterial: function( materialInfo ) { + var $contents = $(this.node).children('.material-contents'); + $contents.empty(); + + var widget = this; + + $(materialInfo).children('Position').each( function() { + var match = $(this).attr('pos').match(/(\d+)(?:-(\d+))?/); + if (!match) return; + + var start = parseInt(match[1]); + var end = ( match[2] ? parseInt(match[2]) : start ) + 1; + var $input; + var $values = $(this).children('Value'); + + if ($values.length == 0) { + $contents.append( '' + widget.getFixed(start, end) + '' ); + return; + } + + if ( match[2] ) { + $input = $( '' ); + } else { + $input = $( '' ); + + $values.each( function() { + $input.append( '' ); + } ); + } + + $contents.append( $input ); + widget.bindFixed( $input, start, end ); + } ); + }, + + nodeChanged: function() { + this.mark.changed(); + var widget = this; + + var $inputs = $(this.node).find('input, select'); + if ( !$inputs.length ) return; + + $inputs.off('keydown.marc-tab'); + var editor = widget.editor; + + $inputs.each( function( i ) { + $(this).on( 'keydown.marc-tab', function( e ) { + if ( e.which != 9 ) return; // Tab + + var span = widget.mark.find(); + var cur = editor.cm.getCursor(); + + if ( e.shiftKey ) { + if ( i > 0 ) { + $inputs.eq(i - 1).trigger( 'focus' ); + } else { + editor.cm.setCursor( span.from ); + // FIXME: ugly hack + editor.cm.options.extraKeys['Shift-Tab']( editor.cm ); + editor.focus(); + } + } else { + if ( i < $inputs.length - 1 ) { + $inputs.eq(i + 1).trigger( 'focus' ); + } else { + editor.cm.setCursor( span.to ); + editor.focus(); + } + } + + return false; + } ); + } ); + }, + + // Template utils + insertTemplate: function( sel ) { + var wsOnly = /^\s*$/; + $( sel ).contents().clone().each( function() { + if ( this.nodeType == Node.TEXT_NODE ) { + this.data = this.data.replace( /^\s+|\s+$/g, '' ); + } + } ).appendTo( this.node ); + }, + }, + + ActivateAt: function( editor, cur, idx ) { + var marks = editor.findMarksAt( cur ); + if ( !marks.length ) return false; + + var $input = $(marks[0].widget.node).find('input, select').eq(idx || 0); + if ( !$input.length ) return false; + + $input.focus(); + return true; + }, + + Notify: function( editor ) { + $.each( editor.cm.getAllMarks(), function( undef, mark ) { + if ( mark.widget && mark.widget.notify ) mark.widget.notify(); + } ); + }, + + UpdateLine: function( editor, line ) { + var info = editor.getLineInfo( { line: line, ch: 0 } ); + var lineh = editor.cm.getLineHandle( line ); + if ( !lineh ) return; + + if ( !info ) { + if ( lineh.markedSpans ) { + $.each( lineh.markedSpans, function ( undef, span ) { + var mark = span.marker; + if ( !mark.widget ) return; + + mark.widget.clearToText(); + } ); + } + return; + } + + var subfields = []; + + var end = editor.cm.getLine( line ).length; + if ( info.tagNumber < '010' ) { + if ( end >= 4 ) subfields.push( { code: '@', from: 4, to: end } ); + } else { + for ( var i = 0; i < info.subfields.length; i++ ) { + var next = ( i < info.subfields.length - 1 ) ? info.subfields[i + 1].ch : end; + subfields.push( { code: info.subfields[i].code, from: info.subfields[i].ch + 3, to: next } ); + } + } + + $.each( subfields, function ( undef, subfield ) { + var id = info.tagNumber + subfield.code; + var marks = editor.cm.findMarksAt( { line: line, ch: subfield.from } ); + + if ( marks.length ) { + if ( marks[0].id == id ) { + return; + } else { + marks[0].widget.clearToText(); + } + } + + if ( !_widgets[id] ) return; + var fullBase = $.extend( Object.create( Widget.Base ), _widgets[id] ); + var widget = Object.create( fullBase ); + + if ( subfield.from == subfield.to ) { + editor.cm.replaceRange( widget.makeTemplate ? widget.makeTemplate() : '', { line: line, ch: subfield.from }, null, 'marcWidgetPrefill' ); + return; // We'll do the actual work when the change event is triggered again + } + + var text = editor.cm.getRange( { line: line, ch: subfield.from }, { line: line, ch: subfield.to } ); + + widget.text = text; + var node = widget.init(); + + var mark = editor.cm.markText( { line: line, ch: subfield.from }, { line: line, ch: subfield.to }, { + inclusiveLeft: false, + inclusiveRight: false, + replacedWith: node, + } ); + + mark.id = id; + mark.widget = widget; + + widget.node = node; + widget.mark = mark; + widget.editor = editor; + + if ( widget.postCreate ) { + widget.postCreate(); + } + + widget.nodeChanged(); + } ); + }, + }; + + return Widget; +} ); diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/cateditor.css b/koha-tmpl/intranet-tmpl/prog/en/css/cateditor.css new file mode 100644 index 0000000..8dd7c06 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/css/cateditor.css @@ -0,0 +1,402 @@ +/*> Infrastructure */ +body { + padding: 0; +} + +#loading { + background-color: #FFF; + cursor: wait; + height: 100%; + left: 0; + opacity: .7; + position: fixed; + top: 0; + width: 100%; + z-index: 1000; +} + +#loading div { + background : transparent url(../../img/loading.gif) top left no-repeat; + font-size : 175%; + font-weight: bold; + height: 2em; + left: 50%; + margin: -1em 0 0 -2.5em; + padding-left : 50px; + position: absolute; + top: 50%; + width: 15em; +} + +#alerts-container { + font-size: 12px; +} + +#alerts-container h3 { + font-size: inherit; +} + +#alerts-container > ul { + padding: 0; +} + +#alerts-container > ul > li { + border-bottom: 1px solid #DDD; + display: block; + padding: 4px 0; +} + +#alerts-container > ul > li:first-child { + padding-top: 0; +} + +#alerts-container > ul > li:last-child { + border-bottom: none; + padding-bottom: 0; +} + +/*> MARC editor */ +#editor .CodeMirror { + line-height: 1.2; +} + +.cm-tagnumber { + color: #080; + font-weight: bold; +} + +.cm-bad-tagnumber { + color: #A20; + font-weight: bold; +} + +.cm-indicator { + color: #884; +} + +.cm-subfieldcode { + background-color: #F4F4F4; + color: #187848; + border-radius: 3px 8px 8px 3px; + border-right: 2px solid white; + font-weight: bold; + margin-right: -2px; +} + +.cm-bad-subfieldcode { + background-color: #FFD9D9; + color: #482828; + border-radius: 3px 8px 8px 3px; + font-weight: bold; +} + +#editor .modified-line-gutter { + width: 10px; +} + +#editor .modified-line { + background: #F8F8F8; + border-left: 5px solid black; + margin-left: -10px; + padding-left: 5px; +} + +#editor .CodeMirror-gutters { + background: transparent; + border-right: none; +} + +/*> MARC editor widgets */ + +#editor .subfield-widget { + color: #538200; + border: solid 2px #538200; + border-radius: 6px; + font-family: inherit; + line-height: 2.75; + margin: 3px 0; + padding: 4px; +} + +#editor .subfield-widget select, #editor .subfield-widget input { + height: 1.5em; + vertical-align: middle; +} + +#editor .subfield-widget select:focus { + outline: 2px #83A230 solid; +} + +#editor .fixed-widget input { + width: 4em; +} + +#editor .fixed-widget select { + width: 3em; +} + +#editor .fixed-widget .material-select { + width: 4.5em; + margin-right: .5em; +} + +#editor .fixed-collapsed { + display: inline-block; + margin: 0 .25em; + text-align: center; + text-decoration: underline; +} + +#editor .hidden-widget { + color: #999999; + border: solid 2px #AAAAAA; + line-height: 2; + padding: 2px; +} + +.structure-error { + background: #FFEEEE; + font-size: 0.9em; + line-height: 1.5; + margin: .5em; + padding: 0 .5em; +} + +.structure-error i { + vertical-align: text-bottom; +} + +#statusbar { + background-color: #F4F8F9; + border: solid 2px #b9d8d9; + border-bottom-style: none; + border-radius: 6px 6px 0 0; + height: 18px; + margin-bottom: -32px; + overflow: auto; + padding: 4px; + padding-bottom: 0; +} + +#statusbar #status-tag-info, #statusbar #status-subfield-info { + float: left; + overflow: hidden; + padding-right: 2%; + width: 48%; +} + +#record-info .label { + float: none; +} + +#record-info .label + span { + display: block; + padding-left: 1em; +} + +/*> Search */ + +#advanced-search-ui, #search-results-ui, #macro-ui { + padding: 5px; + width: 90%; +} + +.modal-body { + height: 400px; + padding: 0; +} + +#quicksearch-overlay { + background: rgba(255, 255, 255, .9); + border: 2px solid #CC8877; + border-radius: 5px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: #664444; + position: relative; + vertical-align: middle; +} + +#quicksearch-overlay h3 { + font-size: 1.5em%; + margin: 0; + text-align: center; + padding: 50px 5px; +} + +#quicksearch-overlay p { + bottom: 0; + font-size: .8em; + overflow: hidden; + padding: 8px 15px; + position: absolute; + text-align: center; +} + +#quicksearch input, #quicksearch a { + font-size: 1.2em; + padding: 3px 0; + width: 96%; // I have no idea why this is necessary +} + +#show-advanced-search { + display: block; + margin-top: .3em; +} + +#advanced-search-fields { + margin: 0; + padding: 0; +} + +#advanced-search-fields li { + display: inline-block; + list-style-type: none; +} + +#advanced-search-fields label { + display: inline-block; + font-weight: bold; + padding: 1em 1em 1em 0; + width: 10em; + text-align: right; +} + +#advanced-search-fields input { + display: inline-block; + margin: 0px auto; + width: 14em; +} + +.icon-loading { + display: inline-block; + height: 16px; + width: 16px; + background: transparent url("../../img/spinner-small.gif") top left no-repeat; + padding: -1px; + vertical-align: text-top; +} + +/*> Search results */ + +#searchresults table { + width: 100%; +} + +.sourcecol { + width: 50px; +} + +.results-info { + height: 100px; + overflow: auto; +} + +.toolscol { + padding: 0; + width: 100px; +} + +.toolscol ul { + margin: 0; + padding: 0; +} + +#searchresults .toolscol li { + list-style-type: none; + list-style-image: none; +} + +.toolscol a { + border-bottom: 1px solid #BCBCBC; + display: block; + padding: 0 1em; + line-height: 24px; +} + +.results-marc { + font-family: monospace; + height: auto; + white-space: pre-wrap; +} + +#searchresults { + position: relative; +} + +#search-overlay { + background: white; + bottom: 0; + font-size: 2em; + left: 0; + opacity: .7; + padding: 2em; + position: absolute; + right: 0; + text-align: center; + top: 0; + z-index: 9001; +} + +/*> Macros */ + +#macro-ui .CodeMirror { + width: 100%; +} + +#macro-save-message { + color: #666; + font-size: 13px; + float: right; + line-height: 26px; +} + +#macro-list > li { + border: 2px solid #F0F0F0; + border-radius: 6px; + display: block; + font-size: 115%; +} + +#macro-list > li + li { + margin-top: -2px; +} + +#macro-list .active { + background: #EDF4F6; + border-color: none; +} + +#macro-list a { + display: block; + padding: 6px; +} + +#macro-list a:focus { + outline: none; +} + +.macro-info { + background-color: #F4F4F4; + display: none; + margin: 0; + padding: 10px; + text-align: right; +} + +.macro-info li { + color: #666; + font-size: 75%; + list-style-type: none; +} + +.macro-info .label { + clear: left; + font-weight: bold; + float: left; +} + +#macro-list .active .macro-info { + display: block; +} diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc new file mode 100644 index 0000000..c0637be --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc @@ -0,0 +1,835 @@ + + + + + + +[% IF marcflavour == 'MARC21' %] +[% PROCESS 'cateditor-widgets-marc21.inc' %] +[% ELSE %] + +[% END %] + + diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc new file mode 100644 index 0000000..bfba1ba --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc @@ -0,0 +1,158 @@ + + + diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt index f446d6e..28b3bbe 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt @@ -100,7 +100,26 @@ redirect("just_save", tab); return false; }); - }); + + $( '#switcheditor' ).click( function() { + var breedingid = [% breedingid || "null" %]; + + if ( !confirm( breedingid ? _("This record cannot be transferred to the advanced editor. Continue?") : _("Any changes will not be saved. Continue?") ) ) return false; + + $.cookie( 'catalogue_editor_[% USER_INFO.0.borrowernumber %]', 'advanced', { expires: 365, path: '/' } ); + + var biblionumber = [% biblionumber || "null" %]; + + if ( biblionumber ) { + window.location = '/cgi-bin/koha/cataloguing/editor.pl#catalog:' + biblionumber; + } else { + window.location = '/cgi-bin/koha/cataloguing/editor.pl'; + } + + return false; + } ); + + }); function redirect(dest){ $("#redirect").attr("value",dest); @@ -457,6 +476,7 @@ function Changefwk(FwkList) { [% UNLESS (circborrowernumber) %][%# Hide in fast cataloging %] + [% IF (biblionumber) %] [% IF ( BiblioDefaultViewmarc ) %]
    diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt index 4f73372..7115b84 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt @@ -15,6 +15,11 @@ PopupZ3950($(this).prop('id')); return false; }); + + $("#useadvanced").click(function(){ + $.cookie( 'catalogue_editor_[% USER_INFO.0.borrowernumber %]', 'advanced', { expires: 365, path: '/' } ); + return true; + }); }); /* this function open a popup to search on z3950 server. */ @@ -48,6 +53,7 @@ [% IF ( CAN_user_editcatalogue_edit_catalogue ) %]
    + Advanced editor