From 3ed5b161ecda04a994d3956c6094f7b72ef87c09 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Sun, 25 Mar 2012 20:08:03 -0700 Subject: [PATCH 01/36] Moved simplewiki autosuggest js (it didn't get migrated). Fixed a minor typo in settings.py (it suggested the wrong override location for the static js files) --- .../media/css/autosuggest_inquisitor.css | 177 ----------- djangoapps/simplewiki/media/css/base.css | 281 ------------------ .../simplewiki/media/css/base_print.css | 6 - .../css/img_inquisitor/_source/as_pointer.png | Bin 27142 -> 0 bytes .../css/img_inquisitor/_source/li_corner.png | Bin 28442 -> 0 bytes .../css/img_inquisitor/_source/ul_corner.png | Bin 27649 -> 0 bytes .../media/css/img_inquisitor/as_pointer.gif | Bin 66 -> 0 bytes .../media/css/img_inquisitor/hl_corner_bl.gif | Bin 73 -> 0 bytes .../media/css/img_inquisitor/hl_corner_br.gif | Bin 73 -> 0 bytes .../media/css/img_inquisitor/hl_corner_tl.gif | Bin 73 -> 0 bytes .../media/css/img_inquisitor/hl_corner_tr.gif | Bin 73 -> 0 bytes .../media/css/img_inquisitor/ul_corner_bl.gif | Bin 49 -> 0 bytes .../media/css/img_inquisitor/ul_corner_br.gif | Bin 49 -> 0 bytes .../media/css/img_inquisitor/ul_corner_tl.gif | Bin 50 -> 0 bytes .../media/css/img_inquisitor/ul_corner_tr.gif | Bin 50 -> 0 bytes .../simplewiki/media/img/box_corner_bl.gif | Bin 49 -> 0 bytes .../simplewiki/media/img/box_corner_br.gif | Bin 49 -> 0 bytes .../simplewiki/media/img/box_corner_tl.gif | Bin 50 -> 0 bytes .../simplewiki/media/img/box_corner_tr.gif | Bin 50 -> 0 bytes djangoapps/simplewiki/media/img/delete.gif | Bin 130 -> 0 bytes .../simplewiki/media/img/delete_grey.gif | Bin 130 -> 0 bytes settings.py | 2 +- .../js/simplewiki}/bsn.AutoSuggest_c_2.0.js | 0 23 files changed, 1 insertion(+), 465 deletions(-) delete mode 100644 djangoapps/simplewiki/media/css/autosuggest_inquisitor.css delete mode 100644 djangoapps/simplewiki/media/css/base.css delete mode 100644 djangoapps/simplewiki/media/css/base_print.css delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/_source/as_pointer.png delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/_source/li_corner.png delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/_source/ul_corner.png delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/as_pointer.gif delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_bl.gif delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_br.gif delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_tl.gif delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_tr.gif delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/ul_corner_bl.gif delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/ul_corner_br.gif delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/ul_corner_tl.gif delete mode 100644 djangoapps/simplewiki/media/css/img_inquisitor/ul_corner_tr.gif delete mode 100644 djangoapps/simplewiki/media/img/box_corner_bl.gif delete mode 100644 djangoapps/simplewiki/media/img/box_corner_br.gif delete mode 100644 djangoapps/simplewiki/media/img/box_corner_tl.gif delete mode 100644 djangoapps/simplewiki/media/img/box_corner_tr.gif delete mode 100644 djangoapps/simplewiki/media/img/delete.gif delete mode 100644 djangoapps/simplewiki/media/img/delete_grey.gif rename {djangoapps/simplewiki/media/js => static/js/simplewiki}/bsn.AutoSuggest_c_2.0.js (100%) diff --git a/djangoapps/simplewiki/media/css/autosuggest_inquisitor.css b/djangoapps/simplewiki/media/css/autosuggest_inquisitor.css deleted file mode 100644 index fc407f6f26..0000000000 --- a/djangoapps/simplewiki/media/css/autosuggest_inquisitor.css +++ /dev/null @@ -1,177 +0,0 @@ -/* -================================================ -autosuggest, inquisitor style -================================================ -*/ - -body -{ - position: relative; -} - - -div.autosuggest -{ - position: absolute; - background-image: url(img_inquisitor/as_pointer.gif); - background-position: top; - background-repeat: no-repeat; - padding: 10px 0 0 0; -} - -div.autosuggest div.as_header, -div.autosuggest div.as_footer -{ - position: relative; - height: 6px; - padding: 0 6px; - background-image: url(img_inquisitor/ul_corner_tr.gif); - background-position: top right; - background-repeat: no-repeat; - overflow: hidden; -} -div.autosuggest div.as_footer -{ - background-image: url(img_inquisitor/ul_corner_br.gif); -} - -div.autosuggest div.as_header div.as_corner, -div.autosuggest div.as_footer div.as_corner -{ - position: absolute; - top: 0; - left: 0; - height: 6px; - width: 6px; - background-image: url(img_inquisitor/ul_corner_tl.gif); - background-position: top left; - background-repeat: no-repeat; -} -div.autosuggest div.as_footer div.as_corner -{ - background-image: url(img_inquisitor/ul_corner_bl.gif); -} -div.autosuggest div.as_header div.as_bar, -div.autosuggest div.as_footer div.as_bar -{ - height: 6px; - overflow: hidden; - background-color: #333; -} - - -div.autosuggest ul -{ - list-style: none; - margin: 0 0 -4px 0; - padding: 0; - overflow: hidden; - background-color: #333; -} - -div.autosuggest ul li -{ - color: #ccc; - padding: 0; - margin: 0 4px 4px; - text-align: left; -} - -div.autosuggest ul li a -{ - color: #ccc; - display: block; - text-decoration: none; - background-color: transparent; - text-shadow: #000 0px 0px 5px; - position: relative; - padding: 0; - width: 100%; -} -div.autosuggest ul li a:hover -{ - background-color: #444; -} -div.autosuggest ul li.as_highlight a:hover -{ - background-color: #1B5CCD; -} - -div.autosuggest ul li a span -{ - display: block; - padding: 3px 6px; - font-weight: bold; -} - -div.autosuggest ul li a span small -{ - font-weight: normal; - color: #999; -} - -div.autosuggest ul li.as_highlight a span small -{ - color: #ccc; -} - -div.autosuggest ul li.as_highlight a -{ - color: #fff; - background-color: #1B5CCD; - background-image: url(img_inquisitor/hl_corner_br.gif); - background-position: bottom right; - background-repeat: no-repeat; -} - -div.autosuggest ul li.as_highlight a span -{ - background-image: url(img_inquisitor/hl_corner_bl.gif); - background-position: bottom left; - background-repeat: no-repeat; -} - -div.autosuggest ul li a .tl, -div.autosuggest ul li a .tr -{ - background-image: transparent; - background-repeat: no-repeat; - width: 6px; - height: 6px; - position: absolute; - top: 0; - padding: 0; - margin: 0; -} -div.autosuggest ul li a .tr -{ - right: 0; -} - -div.autosuggest ul li.as_highlight a .tl -{ - left: 0; - background-image: url(img_inquisitor/hl_corner_tl.gif); - background-position: bottom left; -} - -div.autosuggest ul li.as_highlight a .tr -{ - right: 0; - background-image: url(img_inquisitor/hl_corner_tr.gif); - background-position: bottom right; -} - - - -div.autosuggest ul li.as_warning -{ - font-weight: bold; - text-align: center; -} - -div.autosuggest ul em -{ - font-style: normal; - color: #6EADE7; -} \ No newline at end of file diff --git a/djangoapps/simplewiki/media/css/base.css b/djangoapps/simplewiki/media/css/base.css deleted file mode 100644 index 8c55d0c2f4..0000000000 --- a/djangoapps/simplewiki/media/css/base.css +++ /dev/null @@ -1,281 +0,0 @@ -body -{ - font-family: 'Lucida Sans', 'Sans'; -} - -a img -{ - border: 0; -} - -div#wiki_article a { - color: #06d; - text-decoration: none; -} - -div#wiki_article a:hover { - color: #f82; - text-decoration: underline; -} - -hr -{ - background-color: #def; - height: 2px; - border: 0; -} - -div#wiki_article .toc a -{ - color: #025 -} - -div#wiki_article p -{ - /* font-size: 90%; looks funny when combined with lists/tables */ - line-height: 140%; -} -div#wiki_article h1 -{ - font-size: 200%; - font-weight: normal; - color: #048; -} - -div#wiki_article h2 -{ - font-size: 150%; - font-weight: normal; - color: #025; -} - -div#wiki_article h3 -{ - font-size: 120%; - font-weight: bold; - color: #000; -} - -table -{ - border: 1px solid black; - border-collapse: collapse; - margin: 12px; -} - -table tr.dark -{ - background-color: #F3F3F3; -} - -table thead tr -{ - background-color: #def; - border-bottom: 2px solid black; -} - -table td, th -{ - padding: 6px 10px 6px 10px; - border: 1px solid black; -} - -table thead th -{ - padding-bottom: 8px; - padding-top: 8px; -} - -div#wiki_panel -{ - float: right; -} - -div.wiki_box -{ - width: 230px; - padding: 10px; - color: #fff; - font-size: 80%; -} - -div.wiki_box div.wiki_box_contents -{ background-color: #222; - padding: 5px 10px;} - -div.wiki_box div.wiki_box_header, -div.wiki_box div.wiki_box_footer -{ - position: relative; - height: 6px; - padding: 0 6px; - background-image: url(../img/box_corner_tr.gif); - background-position: top right; - background-repeat: no-repeat; - overflow: hidden; -} - -div.wiki_box div.wiki_box_footer -{ - background-image: url(../img/box_corner_br.gif); -} - -div.wiki_box div.wiki_box_header div.wiki_box_corner, -div.wiki_box div.wiki_box_footer div.wiki_box_corner -{ - position: absolute; - top: 0; - left: 0; - height: 6px; - width: 6px; - background-image: url(../img/box_corner_tl.gif); - background-position: top left; - background-repeat: no-repeat; -} - -div.wiki_box div.wiki_box_footer div.wiki_box_corner -{ - background-image: url(../img/box_corner_bl.gif); -} - -div.wiki_box div.wiki_box_header div.wiki_box_bar, -div.wiki_box div.wiki_box_footer div.wiki_box_bar -{ - height: 6px; - overflow: hidden; - background-color: #222; -} - - -div.wiki_box a -{ - color: #acf; -} - -div.wiki_box p -{ - margin: 5px 0; -} - -div.wiki_box ul -{ - padding-left: 20px; - margin-left: 0; -} - -div.wiki_box div.wiki_box_title -{ - margin-bottom: 5px; - font-size: 140%; -} - -form#wiki_revision #id_contents -{ - width:500px; - height: 400px; - font-family: monospace; -} - -form#wiki_revision #id_title -{ - width: 500px; -} - -form#wiki_revision #id_revision_text -{ - width: 500px; -} - -table#wiki_revision_table -{ - border: none; - border-collapse: collapse; - padding-right: 250px; -} - -table#wiki_revision_table th -{ - border: none; - text-align: left; - vertical-align: top; -} - -table#wiki_revision_table td -{ - border: none; -} - -table#wiki_history_table -{ - border-collapse: collapse; - border-spacing: 0; - padding-right: 250px; -} - -table#wiki_history_table th#modified -{ - width: 220px; -} - -table#wiki_history_table td -{ - border: none; -} - -table#wiki_history_table tbody tr -{ - border-bottom: 1px solid black; -} - -table#wiki_history_table tbody td -{ - vertical-align: top; - padding: 5px; -} - -table#wiki_history_table tfoot td -{ - border: none; -} - -table#wiki_history_table tbody td.diff -{ - font-family: monospace; - overflow: hidden; - border-left: 1px dotted black; - border-right: 1px dotted black; -} - -table#wiki_history_table th -{ - text-align: left; -} - -div#wiki_attach_progress_container -{ - background-color: #333; - width: 100%; - height: 20px; - display: none; -} - -div#wiki_attach_progress -{ - width: 25%; - background-color: #999; -} - -blockquote { - margin-top: 15px; - margin-bottom: 15px; - margin-left: 50px; - padding-left: 15px; - border-left: 3px solid #666; - color: #999; - max-width: 400px ; -} - -blockquote p { - margin-top: 8px; - margin-bottom: 8px; -} diff --git a/djangoapps/simplewiki/media/css/base_print.css b/djangoapps/simplewiki/media/css/base_print.css deleted file mode 100644 index 4c887c8aa8..0000000000 --- a/djangoapps/simplewiki/media/css/base_print.css +++ /dev/null @@ -1,6 +0,0 @@ -div#wiki_panel -{ - display:none; -} - - diff --git a/djangoapps/simplewiki/media/css/img_inquisitor/_source/as_pointer.png b/djangoapps/simplewiki/media/css/img_inquisitor/_source/as_pointer.png deleted file mode 100644 index a0d43aaf0e9d4b9700eac95499f5614ccbd91cd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27142 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

+|Jyh+BejmcwL=I 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 diff --git a/djangoapps/simplewiki/media/css/img_inquisitor/as_pointer.gif b/djangoapps/simplewiki/media/css/img_inquisitor/as_pointer.gif deleted file mode 100644 index dbc21220f6ff01769188756f12651a07d6b4c309..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66 zcmZ?wbhEHb6k*_EXkcJ4Ha7nM|G(l-7DjdkMg|=QAOOiQFiG~*Gn~j>HK!%O$m{Ll T%Az+l_f`9%Ef-}lGFSruI~EhJ diff --git a/djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_bl.gif b/djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_bl.gif deleted file mode 100644 index b701d01c93cc7746369c52cba6c05e5d65946822..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73 zcmZ?wbhEHbWMg1sSj4~}9dlO8DMuk>gORDt|Ns9PU_kLF3nM!N3xf_w0Hl_InUBLu T=k&vg4@D%dJQ9pyVz34PYIYFP diff --git a/djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_br.gif b/djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_br.gif deleted file mode 100644 index 11debd7fec2c4fe5442211abfead3fd107d2f4c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73 zcmZ?wbhEHbWMg1sSj4~}9dlO8DMuk>gORDt|Ns9PU_kLF3nM!N3xf_w0Hl_Ina_i3 T&5=VJ4?I|r6eIY>gTWdAc$pGy diff --git a/djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_tl.gif b/djangoapps/simplewiki/media/css/img_inquisitor/hl_corner_tl.gif deleted file mode 100644 index 1c2bbaf7a7430c4a574a83f2ca27bd7df5770879..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73 zcmZ?wbhEHbWMg1sSj4~}9dlO8DMuk>gQ1!2|Ns9PU_kLF3nM!N3xf_w0Hl_Ina@Is T>DGaR5=uKiOyVeEVz34PW|RgQ1!2|Ns9PU_kLF3nM!N3xf_w0Hl_InU6#0 T@`ppVrX95hg;u09F<1itYmX4% diff --git a/djangoapps/simplewiki/media/css/img_inquisitor/ul_corner_bl.gif b/djangoapps/simplewiki/media/css/img_inquisitor/ul_corner_bl.gif deleted file mode 100644 index 4fa8c8e11b7111278b74b750c6c1fb19cf00c0a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49 zcmZ?wbhEHbWMg1sXkcV8Ha2EpU{L(Y!pOkD$e;sc1I5`G7??O(`o(ritc}xVum%9L CT?d>1 diff --git a/djangoapps/simplewiki/media/css/img_inquisitor/ul_corner_br.gif b/djangoapps/simplewiki/media/css/img_inquisitor/ul_corner_br.gif deleted file mode 100644 index f589431b49668999d9394d1c1f0c30e4c8b3381d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49 zcmZ?wbhEHbWMg1sXkcV8Ha2EpU{L(Y!pOkD$e;sc1I5`G7??O(`ZFiiJXFwOum%9P CLv0}%XHFqXYemi;cx9!_M&ztx2^5y^k|9_f1 zdFs@u3`junCkrD312=;XNDO2K1B-sa+Qf5)PI>`T!d(nRIUgxqqjev@<|JIi^PEuX0%AE`h)&Qu)FYy2X diff --git a/settings.py b/settings.py index f819019122..d4dbd4d79f 100644 --- a/settings.py +++ b/settings.py @@ -12,7 +12,7 @@ DEFAULT_GROUPS = [] STATIC_GRAB = False DEV_CONTENT = True -LIB_URL = '/static/lib/' +LIB_URL = '/static/js/' LIB_URL = 'https://mitxstatic.s3.amazonaws.com/js/' BOOK_URL = '/static/book/' BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/' diff --git a/djangoapps/simplewiki/media/js/bsn.AutoSuggest_c_2.0.js b/static/js/simplewiki/bsn.AutoSuggest_c_2.0.js similarity index 100% rename from djangoapps/simplewiki/media/js/bsn.AutoSuggest_c_2.0.js rename to static/js/simplewiki/bsn.AutoSuggest_c_2.0.js From 8fce7066dafe6d89039703138ce957e38ccc98b1 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Sun, 25 Mar 2012 20:39:21 -0700 Subject: [PATCH 02/36] You can now edit a circuit directly on the edit page of the wiki. These edits show up in the history. This hasn't been tested much. --- djangoapps/simplewiki/mdx_circuit.py | 11 +- static/js/CodeMirror/codemirror.css | 112 + static/js/CodeMirror/codemirror.js | 3015 +++++++++++++++++++++++++ static/js/CodeMirror/mitx_markdown.js | 345 +++ static/js/CodeMirror/xml.js | 267 +++ templates/simplewiki_base.html | 2 +- templates/simplewiki_edit.html | 56 +- 7 files changed, 3789 insertions(+), 19 deletions(-) create mode 100644 static/js/CodeMirror/codemirror.css create mode 100644 static/js/CodeMirror/codemirror.js create mode 100644 static/js/CodeMirror/mitx_markdown.js create mode 100644 static/js/CodeMirror/xml.js diff --git a/djangoapps/simplewiki/mdx_circuit.py b/djangoapps/simplewiki/mdx_circuit.py index 465e7d9a3a..33377eb019 100755 --- a/djangoapps/simplewiki/mdx_circuit.py +++ b/djangoapps/simplewiki/mdx_circuit.py @@ -30,15 +30,14 @@ class CircuitExtension(markdown.Extension): md.inlinePatterns.add(name, pattern, "[a-zA-Z0-9]*)$') + self.add_inline(md, 'circuit', CircuitLink, r'^circuit-schematic:(?P.*)$') class CircuitLink(markdown.inlinepatterns.Pattern): def handleMatch(self, m): - name = m.group('name') - if not name.isalnum(): - return etree.fromstring("

Circuit name must be alphanumeric
") - - return etree.fromstring(render_to_string('show_circuit.html', {'name':name})) + data = m.group('data') + + ##TODO: We need to html escape the data + return etree.fromstring("") def makeExtension(configs=None) : diff --git a/static/js/CodeMirror/codemirror.css b/static/js/CodeMirror/codemirror.css new file mode 100644 index 0000000000..2d79f4aa79 --- /dev/null +++ b/static/js/CodeMirror/codemirror.css @@ -0,0 +1,112 @@ +.CodeMirror { + line-height: 1em; + font-family: monospace; +} + +.CodeMirror-scroll { + overflow: auto; + height: 300px; + /* This is needed to prevent an IE[67] bug where the scrolled content + is visible outside of the scrolling box. */ + position: relative; + outline: none; +} + +.CodeMirror-gutter { + position: absolute; left: 0; top: 0; + z-index: 10; + background-color: #f7f7f7; + border-right: 1px solid #eee; + min-width: 2em; + height: 100%; +} +.CodeMirror-gutter-text { + color: #aaa; + text-align: right; + padding: .4em .2em .4em .4em; + white-space: pre !important; +} +.CodeMirror-lines { + padding: .4em; + white-space: pre; +} + +.CodeMirror pre { + -moz-border-radius: 0; + -webkit-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + border-width: 0; margin: 0; padding: 0; background: transparent; + font-family: inherit; + font-size: inherit; + padding: 0; margin: 0; + white-space: pre; + word-wrap: normal; +} + +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; +} +.CodeMirror-wrap .CodeMirror-scroll { + overflow-x: hidden; +} + +.CodeMirror textarea { + outline: none !important; +} + +.CodeMirror pre.CodeMirror-cursor { + z-index: 10; + position: absolute; + visibility: hidden; + border-left: 1px solid black; + border-right:none; + width:0; +} +.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} +.CodeMirror-focused pre.CodeMirror-cursor { + visibility: visible; +} + +div.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } + +.CodeMirror-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* Default theme */ + +.cm-s-default span.cm-keyword {color: #708;} +.cm-s-default span.cm-atom {color: #219;} +.cm-s-default span.cm-number {color: #164;} +.cm-s-default span.cm-def {color: #00f;} +.cm-s-default span.cm-variable {color: black;} +.cm-s-default span.cm-variable-2 {color: #05a;} +.cm-s-default span.cm-variable-3 {color: #085;} +.cm-s-default span.cm-property {color: black;} +.cm-s-default span.cm-operator {color: black;} +.cm-s-default span.cm-comment {color: #a50;} +.cm-s-default span.cm-string {color: #a11;} +.cm-s-default span.cm-string-2 {color: #f50;} +.cm-s-default span.cm-meta {color: #555;} +.cm-s-default span.cm-error {color: #f00;} +.cm-s-default span.cm-qualifier {color: #555;} +.cm-s-default span.cm-builtin {color: #30a;} +.cm-s-default span.cm-bracket {color: #cc7;} +.cm-s-default span.cm-tag {color: #170;} +.cm-s-default span.cm-attribute {color: #00c;} +.cm-s-default span.cm-header {color: #a0a;} +.cm-s-default span.cm-quote {color: #090;} +.cm-s-default span.cm-hr {color: #999;} +.cm-s-default span.cm-link {color: #00c;} + +span.cm-header, span.cm-strong {font-weight: bold;} +span.cm-em {font-style: italic;} +span.cm-emstrong {font-style: italic; font-weight: bold;} +span.cm-link {text-decoration: underline;} + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js new file mode 100644 index 0000000000..c7d3c86899 --- /dev/null +++ b/static/js/CodeMirror/codemirror.js @@ -0,0 +1,3015 @@ +// All functions that need access to the editor's state live inside +// the CodeMirror function. Below that, at the bottom of the file, +// some utilities are defined. + +// CodeMirror is the only global var we claim +var CodeMirror = (function() { + // This is the function that produces an editor instance. Its + // closure is used to store the editor state. + function CodeMirror(place, givenOptions) { + // Determine effective options based on given values and defaults. + var options = {}, defaults = CodeMirror.defaults; + for (var opt in defaults) + if (defaults.hasOwnProperty(opt)) + options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; + + // The element in which the editor lives. + var wrapper = document.createElement("div"); + wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""); + // This mess creates the base DOM structure for the editor. + wrapper.innerHTML = + '
' + // Wraps and hides input textarea + '
' + + '
' + + '
' + // Set to the height of the text, causes scrolling + '
' + // Moved around its parent to cover visible view + '
' + + // Provides positioning relative to (visible) text origin + '
' + + '
' + + '
 
' + // Absolutely positioned blinky cursor + '
' + // DIVs containing the selection and the actual code + '
'; + if (place.appendChild) place.appendChild(wrapper); else place(wrapper); + // I've never seen more elegant code in my life. + var inputDiv = wrapper.firstChild, input = inputDiv.firstChild, + scroller = wrapper.lastChild, code = scroller.firstChild, + mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild, + lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild, + cursor = measure.nextSibling, selectionDiv = cursor.nextSibling, + lineDiv = selectionDiv.nextSibling; + themeChanged(); + // Needed to hide big blue blinking cursor on Mobile Safari + if (ios) input.style.width = "0px"; + if (!webkit) lineSpace.draggable = true; + lineSpace.style.outline = "none"; + if (options.tabindex != null) input.tabIndex = options.tabindex; + if (options.autofocus) focusInput(); + if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; + // Needed to handle Tab key in KHTML + if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute"; + + // Check for problem with IE innerHTML not working when we have a + // P (or similar) parent node. + try { stringWidth("x"); } + catch (e) { + if (e.message.match(/runtime/i)) + e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)"); + throw e; + } + + // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. + var poll = new Delayed(), highlight = new Delayed(), blinker; + + // mode holds a mode API object. doc is the tree of Line objects, + // work an array of lines that should be parsed, and history the + // undo history (instance of History constructor). + var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused; + loadMode(); + // The selection. These are always maintained to point at valid + // positions. Inverted is used to remember that the user is + // selecting bottom-to-top. + var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; + // Selection-related flags. shiftSelecting obviously tracks + // whether the user is holding shift. + var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText, + overwrite = false, suppressEdits = false; + // Variables used by startOperation/endOperation to track what + // happened during the operation. + var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone, + gutterDirty, callbacks; + // Current visible range (may be bigger than the view window). + var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; + // bracketHighlighted is used to remember that a bracket has been + // marked. + var bracketHighlighted; + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + var maxLine = "", maxWidth; + var tabCache = {}; + + // Initialize the content. + operation(function(){setValue(options.value || ""); updateInput = false;})(); + var history = new History(); + + // Register our event handlers. + connect(scroller, "mousedown", operation(onMouseDown)); + connect(scroller, "dblclick", operation(onDoubleClick)); + connect(lineSpace, "dragstart", onDragStart); + connect(lineSpace, "selectstart", e_preventDefault); + // 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 (!gecko) connect(scroller, "contextmenu", onContextMenu); + connect(scroller, "scroll", function() { + lastScrollPos = scroller.scrollTop; + updateDisplay([]); + if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; + if (options.onScroll) options.onScroll(instance); + }); + connect(window, "resize", function() {updateDisplay(true);}); + connect(input, "keyup", operation(onKeyUp)); + connect(input, "input", fastPoll); + connect(input, "keydown", operation(onKeyDown)); + connect(input, "keypress", operation(onKeyPress)); + connect(input, "focus", onFocus); + connect(input, "blur", onBlur); + + connect(scroller, "dragenter", e_stop); + connect(scroller, "dragover", e_stop); + connect(scroller, "drop", operation(onDrop)); + connect(scroller, "paste", function(){focusInput(); fastPoll();}); + connect(input, "paste", fastPoll); + connect(input, "cut", operation(function(){ + if (!options.readOnly) replaceSelection(""); + })); + + // Needed to handle Tab key in KHTML + if (khtml) connect(code, "mouseup", function() { + if (document.activeElement == input) input.blur(); + focusInput(); + }); + + // IE throws unspecified error in certain cases, when + // trying to access activeElement before onload + var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { } + if (hasFocus || options.autofocus) setTimeout(onFocus, 20); + else onBlur(); + + function isLine(l) {return l >= 0 && l < doc.size;} + // The instance object that we'll return. Mostly calls out to + // local functions in the CodeMirror function. Some do some extra + // range checking and/or clipping. operation is used to wrap the + // call so that changes it makes are tracked, and the display is + // updated afterwards. + var instance = wrapper.CodeMirror = { + getValue: getValue, + setValue: operation(setValue), + getSelection: getSelection, + replaceSelection: operation(replaceSelection), + focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();}, + setOption: function(option, value) { + var oldVal = options[option]; + options[option] = value; + if (option == "mode" || option == "indentUnit") loadMode(); + else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();} + else if (option == "readOnly" && !value) {resetInput(true);} + else if (option == "theme") themeChanged(); + else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); + else if (option == "tabSize") updateDisplay(true); + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") { + gutterChanged(); + updateDisplay(true); + } + }, + getOption: function(option) {return options[option];}, + undo: operation(undo), + redo: operation(redo), + indentLine: operation(function(n, dir) { + if (typeof dir != "string") { + if (dir == null) dir = options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(n)) indentLine(n, dir); + }), + indentSelection: operation(indentSelected), + historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, + clearHistory: function() {history = new History();}, + matchBrackets: operation(function(){matchBrackets(true);}), + getTokenAt: operation(function(pos) { + pos = clipPos(pos); + return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch); + }), + getStateAfter: function(line) { + line = clipLine(line == null ? doc.size - 1: line); + return getStateBefore(line + 1); + }, + cursorCoords: function(start, mode) { + if (start == null) start = sel.inverted; + return this.charCoords(start ? sel.from : sel.to, mode); + }, + charCoords: function(pos, mode) { + pos = clipPos(pos); + if (mode == "local") return localCoords(pos, false); + if (mode == "div") return localCoords(pos, true); + return pageCoords(pos); + }, + coordsChar: function(coords) { + var off = eltOffset(lineSpace); + return coordsChar(coords.x - off.left, coords.y - off.top); + }, + markText: operation(markText), + setBookmark: setBookmark, + findMarksAt: findMarksAt, + setMarker: operation(addGutterMarker), + clearMarker: operation(removeGutterMarker), + setLineClass: operation(setLineClass), + hideLine: operation(function(h) {return setLineHidden(h, true);}), + showLine: operation(function(h) {return setLineHidden(h, false);}), + onDeleteLine: function(line, f) { + if (typeof line == "number") { + if (!isLine(line)) return null; + line = getLine(line); + } + (line.handlers || (line.handlers = [])).push(f); + return line; + }, + lineInfo: lineInfo, + addWidget: function(pos, node, scroll, vert, horiz) { + pos = localCoords(clipPos(pos)); + var top = pos.yBot, left = pos.x; + node.style.position = "absolute"; + code.appendChild(node); + if (vert == "over") top = pos.y; + else if (vert == "near") { + var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), + hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft(); + if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) + top = pos.y - node.offsetHeight; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = (top + paddingTop()) + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = code.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2; + node.style.left = (left + paddingLeft()) + "px"; + } + if (scroll) + scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + lineCount: function() {return doc.size;}, + clipPos: clipPos, + getCursor: function(start) { + if (start == null) start = sel.inverted; + return copyPos(start ? sel.from : sel.to); + }, + somethingSelected: function() {return !posEq(sel.from, sel.to);}, + setCursor: operation(function(line, ch, user) { + if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); + else setCursor(line, ch, user); + }), + setSelection: operation(function(from, to, user) { + (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); + }), + getLine: function(line) {if (isLine(line)) return getLine(line).text;}, + getLineHandle: function(line) {if (isLine(line)) return getLine(line);}, + setLine: operation(function(line, text) { + if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + }), + removeLine: operation(function(line) { + if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); + }), + replaceRange: operation(replaceRange), + getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, + + triggerOnKeyDown: operation(onKeyDown), + execCommand: function(cmd) {return commands[cmd](instance);}, + // Stuff used by commands, probably not much use to outside code. + moveH: operation(moveH), + deleteH: operation(deleteH), + moveV: operation(moveV), + toggleOverwrite: function() { + if(overwrite){ + overwrite = false; + cursor.className = cursor.className.replace(" CodeMirror-overwrite", ""); + } else { + overwrite = true; + cursor.className += " CodeMirror-overwrite"; + } + }, + + posFromIndex: function(off) { + var lineNo = 0, ch; + doc.iter(0, doc.size, function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos({line: lineNo, ch: ch}); + }, + indexFromPos: function (coords) { + if (coords.line < 0 || coords.ch < 0) return 0; + var index = coords.ch; + doc.iter(0, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + scrollTo: function(x, y) { + if (x != null) scroller.scrollLeft = x; + if (y != null) scroller.scrollTop = y; + updateDisplay([]); + }, + + operation: function(f){return operation(f)();}, + refresh: function(){ + updateDisplay(true); + if (scroller.scrollHeight > lastScrollPos) + scroller.scrollTop = lastScrollPos; + }, + getInputField: function(){return input;}, + getWrapperElement: function(){return wrapper;}, + getScrollerElement: function(){return scroller;}, + getGutterElement: function(){return gutter;} + }; + + function getLine(n) { return getLineAt(doc, n); } + function updateLineHeight(line, height) { + gutterDirty = true; + var diff = height - line.height; + for (var n = line; n; n = n.parent) n.height += diff; + } + + function setValue(code) { + var top = {line: 0, ch: 0}; + updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, + splitLines(code), top, top); + updateInput = true; + } + function getValue(code) { + var text = []; + doc.iter(0, doc.size, function(line) { text.push(line.text); }); + return text.join("\n"); + } + + function onMouseDown(e) { + setShift(e_prop(e, "shiftKey")); + // Check whether this is a click in a widget + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == code && n != mover) return; + + // See if this is a click in the gutter + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) { + if (options.onGutterClick) + options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); + return e_preventDefault(e); + } + + var start = posFromMouse(e); + + switch (e_button(e)) { + case 3: + if (gecko && !mac) onContextMenu(e); + return; + case 2: + if (start) setCursor(start.line, start.ch, true); + 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) == scroller) e_preventDefault(e); return;} + + if (!focused) onFocus(); + + var now = +new Date; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { + e_preventDefault(e); + setTimeout(focusInput, 20); + return selectLine(start.line); + } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { + lastDoubleClick = {time: now, pos: start}; + e_preventDefault(e); + return selectWordAt(start); + } else { lastClick = {time: now, pos: start}; } + + var last = start, going; + if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && + !posLess(start, sel.from) && !posLess(sel.to, start)) { + // Let the drag handler handle this. + if (webkit) lineSpace.draggable = true; + var up = connect(document, "mouseup", operation(function(e2) { + if (webkit) lineSpace.draggable = false; + draggingText = false; + up(); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + setCursor(start.line, start.ch, true); + focusInput(); + } + }), true); + draggingText = true; + // IE's approach to draggable + if (lineSpace.dragDrop) lineSpace.dragDrop(); + return; + } + e_preventDefault(e); + setCursor(start.line, start.ch, true); + + function extend(e) { + var cur = posFromMouse(e, true); + if (cur && !posEq(cur, last)) { + if (!focused) onFocus(); + last = cur; + setSelectionUser(start, cur); + updateInput = false; + var visible = visibleLines(); + if (cur.line >= visible.to || cur.line < visible.from) + going = setTimeout(operation(function(){extend(e);}), 150); + } + } + + function done(e) { + clearTimeout(going); + var cur = posFromMouse(e); + if (cur) setSelectionUser(start, cur); + e_preventDefault(e); + focusInput(); + updateInput = true; + move(); up(); + } + var move = connect(document, "mousemove", operation(function(e) { + clearTimeout(going); + e_preventDefault(e); + if (!ie && !e_button(e)) done(e); + else extend(e); + }), true); + var up = connect(document, "mouseup", operation(done), true); + } + function onDoubleClick(e) { + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) return e_preventDefault(e); + var start = posFromMouse(e); + if (!start) return; + lastDoubleClick = {time: +new Date, pos: start}; + e_preventDefault(e); + selectWordAt(start); + } + function onDrop(e) { + e.preventDefault(); + var pos = posFromMouse(e, true), files = e.dataTransfer.files; + if (!pos || options.readOnly) return; + if (files && files.length && window.FileReader && window.File) { + function loadFile(file, i) { + var reader = new FileReader; + reader.onload = function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(pos); + operation(function() { + var end = replaceRange(text.join(""), pos, pos); + setSelectionUser(pos, end); + })(); + } + }; + reader.readAsText(file); + } + var n = files.length, text = Array(n), read = 0; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } + else { + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, pos); + if (draggingText) replaceRange("", curFrom, curTo); + replaceSelection(text); + focusInput(); + } + } + catch(e){} + } + } + function onDragStart(e) { + var txt = getSelection(); + e.dataTransfer.setData("Text", txt); + + // Use dummy image instead of default browsers image. + if (gecko || chrome) { + var img = document.createElement('img'); + img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image + e.dataTransfer.setDragImage(img, 0, 0); + } + } + + function doHandleBinding(bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + var prevShift = shiftSelecting; + try { + if (options.readOnly) suppressEdits = true; + if (dropShift) shiftSelecting = null; + bound(instance); + } catch(e) { + if (e != Pass) throw e; + return false; + } finally { + shiftSelecting = prevShift; + suppressEdits = false; + } + return true; + } + function handleKeyBinding(e) { + // Handle auto keymap transitions + var startMap = getKeyMap(options.keyMap), next = startMap.auto; + clearTimeout(maybeTransition); + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { + if (getKeyMap(options.keyMap) == startMap) { + options.keyMap = (next.call ? next.call(null, instance) : next); + } + }, 50); + + var name = keyNames[e_prop(e, "keyCode")], handled = false; + if (name == null || e.altGraphKey) return false; + if (e_prop(e, "altKey")) name = "Alt-" + name; + if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; + if (e_prop(e, "metaKey")) name = "Cmd-" + name; + + if (e_prop(e, "shiftKey")) { + handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, + function(b) {return doHandleBinding(b, true);}) + || lookupKey(name, options.extraKeys, options.keyMap, function(b) { + if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b); + }); + } else { + handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding); + } + if (handled) { + e_preventDefault(e); + if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } + } + return handled; + } + function handleCharBinding(e, ch) { + var handled = lookupKey("'" + ch + "'", options.extraKeys, + options.keyMap, doHandleBinding); + if (handled) e_preventDefault(e); + return handled; + } + + var lastStoppedKey = null, maybeTransition; + function onKeyDown(e) { + if (!focused) onFocus(); + if (ie && e.keyCode == 27) { e.returnValue = false; } + if (pollingFast) { if (readInput()) pollingFast = false; } + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var code = e_prop(e, "keyCode"); + // IE does strange things with escape. + setShift(code == 16 || e_prop(e, "shiftKey")); + // First give onKeyEvent option a chance to handle this. + var handled = handleKeyBinding(e); + if (window.opera) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) + replaceSelection(""); + } + } + function onKeyPress(e) { + if (pollingFast) readInput(); + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); + if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (((window.opera && !e.which) || khtml) && handleKeyBinding(e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { + if (mode.electricChars.indexOf(ch) > -1) + setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); + } + if (handleCharBinding(e, ch)) return; + fastPoll(); + } + function onKeyUp(e) { + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + if (e_prop(e, "keyCode") == 16) shiftSelecting = null; + } + + function onFocus() { + if (options.readOnly == "nocursor") return; + if (!focused) { + if (options.onFocus) options.onFocus(instance); + focused = true; + if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) + wrapper.className += " CodeMirror-focused"; + if (!leaveInputAlone) resetInput(true); + } + slowPoll(); + restartBlink(); + } + function onBlur() { + if (focused) { + if (options.onBlur) options.onBlur(instance); + focused = false; + if (bracketHighlighted) + operation(function(){ + if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } + })(); + wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); + } + clearInterval(blinker); + setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); + } + + // Replace the range from from to to by the strings in newText. + // Afterwards, set the selection to selFrom, selTo. + function updateLines(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; + if (history) { + var old = []; + doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); }); + history.addChange(from.line, newText.length, old); + while (history.done.length > options.undoDepth) history.done.shift(); + } + updateLinesNoUndo(from, to, newText, selFrom, selTo); + } + function unredoHelper(from, to) { + if (!from.length) return; + var set = from.pop(), out = []; + for (var i = set.length - 1; i >= 0; i -= 1) { + var change = set[i]; + var replaced = [], end = change.start + change.added; + doc.iter(change.start, end, function(line) { replaced.push(line.text); }); + out.push({start: change.start, added: change.old.length, old: replaced}); + var pos = clipPos({line: change.start + change.old.length - 1, + ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); + updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos); + } + updateInput = true; + to.push(out); + } + function undo() {unredoHelper(history.done, history.undone);} + function redo() {unredoHelper(history.undone, history.done);} + + function updateLinesNoUndo(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; + var recomputeMaxLength = false, maxLineLength = maxLine.length; + if (!options.lineWrapping) + doc.iter(from.line, to.line, function(line) { + if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} + }); + if (from.line != to.line || newText.length > 1) gutterDirty = true; + + var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); + // First adjust the line structure, taking some care to leave highlighting intact. + if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = [], prevLine = null; + if (from.line) { + prevLine = getLine(from.line - 1); + prevLine.fixMarkEnds(lastLine); + } else lastLine.fixMarkStarts(); + for (var i = 0, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], prevLine)); + if (nlines) doc.remove(from.line, nlines, callbacks); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (newText.length == 1) + firstLine.replace(from.ch, to.ch, newText[0]); + else { + lastLine = firstLine.split(to.ch, newText[newText.length-1]); + firstLine.replace(from.ch, null, newText[0]); + firstLine.fixMarkEnds(lastLine); + var added = []; + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + added.push(lastLine); + doc.insert(from.line + 1, added); + } + } else if (newText.length == 1) { + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, ""); + firstLine.append(lastLine); + doc.remove(from.line + 1, nlines, callbacks); + } else { + var added = []; + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, newText[newText.length-1]); + firstLine.fixMarkEnds(lastLine); + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); + doc.insert(from.line + 1, added); + } + if (options.lineWrapping) { + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(from.line, from.line + newText.length, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != line.height) updateLineHeight(line, guess); + }); + } else { + doc.iter(from.line, i + newText.length, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLine = l; maxLineLength = l.length; maxWidth = null; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) { + maxLineLength = 0; maxLine = ""; maxWidth = null; + doc.iter(0, doc.size, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLineLength = l.length; maxLine = l; + } + }); + } + } + + // Add these lines to the work array, so that they will be + // highlighted. Adjust work lines if lines were added/removed. + var newWork = [], lendiff = newText.length - nlines - 1; + for (var i = 0, l = work.length; i < l; ++i) { + var task = work[i]; + if (task < from.line) newWork.push(task); + else if (task > to.line) newWork.push(task + lendiff); + } + var hlEnd = from.line + Math.min(newText.length, 500); + highlightLines(from.line, hlEnd); + newWork.push(hlEnd); + work = newWork; + startWorker(100); + // Remember that these lines changed, for updating the display + changes.push({from: from.line, to: to.line + 1, diff: lendiff}); + var changeObj = {from: from, to: to, text: newText}; + if (textChanged) { + for (var cur = textChanged; cur.next; cur = cur.next) {} + cur.next = changeObj; + } else textChanged = changeObj; + + // Update the selection + function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} + setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); + + // Make sure the scroll-size div has the correct height. + if (scroller.clientHeight) + code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px"; + } + + function replaceRange(code, from, to) { + from = clipPos(from); + if (!to) to = from; else to = clipPos(to); + code = splitLines(code); + function adjustPos(pos) { + if (posLess(pos, from)) return pos; + if (!posLess(to, pos)) return end; + var line = pos.line + code.length - (to.line - from.line) - 1; + var ch = pos.ch; + if (pos.line == to.line) + ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0)); + return {line: line, ch: ch}; + } + var end; + replaceRange1(code, from, to, function(end1) { + end = end1; + return {from: adjustPos(sel.from), to: adjustPos(sel.to)}; + }); + return end; + } + function replaceSelection(code, collapse) { + var reposition = false; + if (code.length > 0) { + var fromLine = getLine(sel.from.line), toLine = getLine(sel.to.line); + if (fromLine.isWidgetBlock && sel.from.ch == fromLine.text.length) { + code = "\n" + code; + } else if (toLine.isWidgetBlock && sel.to.ch == 0) { + code = code + "\n"; + reposition = true; + } + } + + replaceRange1(splitLines(code), sel.from, sel.to, function(end) { + if (collapse == "end") return {from: end, to: end}; + else if (collapse == "start") return {from: sel.from, to: sel.from}; + else return {from: sel.from, to: end}; + }); + if (reposition) moveH(-1, "char"); + } + function replaceRange1(code, from, to, computeSel) { + var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length; + var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); + updateLines(from, to, code, newSel.from, newSel.to); + } + + function getRange(from, to) { + var l1 = from.line, l2 = to.line; + if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); + var code = [getLine(l1).text.slice(from.ch)]; + doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); + code.push(getLine(l2).text.slice(0, to.ch)); + return code.join("\n"); + } + function getSelection() { + return getRange(sel.from, sel.to); + } + + var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll + function slowPoll() { + if (pollingFast) return; + poll.set(options.pollInterval, function() { + startOperation(); + readInput(); + if (focused) slowPoll(); + endOperation(); + }); + } + function fastPoll() { + var missed = false; + pollingFast = true; + function p() { + startOperation(); + var changed = readInput(); + if (!changed && !missed) {missed = true; poll.set(60, p);} + else {pollingFast = false; slowPoll();} + endOperation(); + } + 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.) + var prevInput = ""; + function readInput() { + if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false; + var text = input.value; + if (text == prevInput) return false; + shiftSelecting = null; + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput[same] == text[same]) ++same; + if (same < prevInput.length) + sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; + else if (overwrite && posEq(sel.from, sel.to)) + sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; + replaceSelection(text.slice(same), "end"); + prevInput = text; + return true; + } + function resetInput(user) { + if (!posEq(sel.from, sel.to)) { + prevInput = ""; + input.value = getSelection(); + selectInput(input); + } else if (user) prevInput = input.value = ""; + } + + function focusInput() { + if (options.readOnly != "nocursor") input.focus(); + } + + function scrollEditorIntoView() { + if (!cursor.getBoundingClientRect) return; + var rect = cursor.getBoundingClientRect(); + // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden + if (ie && rect.top == rect.bottom) return; + var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); + if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView(); + } + function scrollCursorIntoView() { + var cursor = localCoords(sel.inverted ? sel.from : sel.to); + var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; + return scrollIntoView(x, cursor.y, x, cursor.yBot); + } + function scrollIntoView(x1, y1, x2, y2) { + var pl = paddingLeft(), pt = paddingTop(); + y1 += pt; y2 += pt; x1 += pl; x2 += pl; + var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; + if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1); scrolled = true;} + else if (y2 > screentop + screen) {scroller.scrollTop = y2 - screen; scrolled = true;} + + var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; + var gutterw = options.fixedGutter ? gutter.clientWidth : 0; + if (x1 < screenleft + gutterw) { + if (x1 < 50) x1 = 0; + scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw); + scrolled = true; + } + else if (x2 > screenw + screenleft - 3) { + scroller.scrollLeft = x2 + 10 - screenw; + scrolled = true; + if (x2 > code.clientWidth) result = false; + } + if (scrolled && options.onScroll) options.onScroll(instance); + return result; + } + + function visibleLines() { + var lh = textHeight(), top = scroller.scrollTop - paddingTop(); + var from_height = Math.max(0, Math.floor(top / lh)); + var to_height = Math.ceil((top + scroller.clientHeight) / lh); + return {from: lineAtHeight(doc, from_height), + to: lineAtHeight(doc, to_height)}; + } + // Uses a set of changes plus the current scroll position to + // determine which DOM updates have to be made, and makes the + // updates. + function updateDisplay(changes, suppressCallback) { + if (!scroller.clientWidth) { + showingFrom = showingTo = displayOffset = 0; + return; + } + // Compute the new visible window + var visible = visibleLines(); + // Bail out if the visible area is already rendered and nothing changed. + if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return; + var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); + if (showingFrom < from && from - showingFrom < 20) from = showingFrom; + if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); + + // Create a range of theoretically intact lines, and punch holes + // in that using the change info. + var intact = changes === true ? [] : + computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes); + // 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.domStart += (from - range.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) return; + intact.sort(function(a, b) {return a.domStart - b.domStart;}); + + var th = textHeight(), gutterDisplay = gutter.style.display; + //lineDiv.style.display = "none"; + patchDisplay(from, to, intact); + lineDiv.style.display = gutter.style.display = ""; + + // Position the mover div to align with the lines it's supposed + // to be showing (which will cover the visible display) + var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; + // This is just a bogus formula that detects when the editor is + // resized or the font size changes. + if (different) lastSizeC = scroller.clientHeight + th; + showingFrom = from; showingTo = to; + displayOffset = heightAtLine(doc, from); + mover.style.top = (displayOffset * th) + "px"; + if (scroller.clientHeight) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + + // Since this is all rather error prone, it is honoured with the + // only assertion in the whole file. + if (lineDiv.childNodes.length != showingTo - showingFrom) + throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + + " nodes=" + lineDiv.childNodes.length); + + if (options.lineWrapping) { + maxWidth = scroller.clientWidth; + var curNode = lineDiv.firstChild, heightChanged = false; + doc.iter(showingFrom, showingTo, function(line) { + if (!line.hidden) { + var height = Math.round(curNode.offsetHeight / th) || 1; + if (line.height != height) { + updateLineHeight(line, height); + gutterDirty = heightChanged = true; + } + } + curNode = curNode.nextSibling; + }); + if (heightChanged) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + } else { + if (maxWidth == null) maxWidth = stringWidth(maxLine); + if (maxWidth > scroller.clientWidth) { + lineSpace.style.width = maxWidth + "px"; + // Needed to prevent odd wrapping/hiding of widgets placed in here. + code.style.width = ""; + code.style.width = scroller.scrollWidth + "px"; + } else { + lineSpace.style.width = code.style.width = ""; + } + } + gutter.style.display = gutterDisplay; + if (different || gutterDirty) updateGutter(); + updateSelection(); + if (!suppressCallback && options.onUpdate) options.onUpdate(instance); + return true; + } + + 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, + domStart: range.domStart}); + 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, domStart: range.domStart}); + if (change.to < range.to) + intact2.push({from: change.to + diff, to: range.to + diff, + domStart: range.domStart + (change.to - range.from)}); + } + } + intact = intact2; + } + return intact; + } + + function patchDisplay(from, to, intact) { + // The first pass removes the DOM nodes that aren't intact. + if (!intact.length) lineDiv.innerHTML = ""; + else { + function killNode(node) { + var tmp = node.nextSibling; + node.parentNode.removeChild(node); + return tmp; + } + var domPos = 0, curNode = lineDiv.firstChild, n; + for (var i = 0; i < intact.length; ++i) { + var cur = intact[i]; + while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;} + for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;} + } + while (curNode) curNode = killNode(curNode); + } + // This pass fills in the lines that actually changed. + var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; + var scratch = document.createElement("div"); + var text_height = textHeight(); //Remove this once heights are in pixels instead of lines + doc.iter(from, to, function(line) { + if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); + if (!nextIntact || nextIntact.from > j) { + if (line.hidden) var html = scratch.innerHTML = "
";
+          else {
+            var html = line.getHTML(makeTab);
+            if (!line.isWidgetBlock) {
+              html = '' + html + '';
+            }
+            // Kludge to make sure the styled element lies behind the selection (by z-index)
+            if (line.bgClassName)
+              html = '
 
' + html + "
"; + } + scratch.innerHTML = html; + var insertChild = scratch.firstChild; + lineDiv.insertBefore(insertChild, curNode); + line.nodeAdded(insertChild); + line.setHeight( insertChild.clientHeight / text_height ); + } else { + curNode = curNode.nextSibling; + } + ++j; + }); + } + + function updateGutter() { + if (!options.gutter && !options.lineNumbers) return; + var hText = mover.offsetHeight, hEditor = scroller.clientHeight; + gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; + var html = [], i = showingFrom, normalNode; + doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { + if (line.hidden) { + html.push("
");
+        } else {
+          var marker = line.gutterMarker;
+          var text = options.lineNumbers ? i + options.firstLineNumber : null;
+          if (marker && marker.text)
+            text = marker.text.replace("%N%", text != null ? text : "");
+          else if (text == null)
+            text = "\u00a0";
+          html.push((marker && marker.style ? '
' : "
"), text);
+          for (var j = 1; j < line.height; ++j) html.push("
 "); + html.push("
"); + if (!marker) normalNode = i; + } + ++i; + }); + gutter.style.display = "none"; + gutterText.innerHTML = html.join(""); + // Make sure scrolling doesn't cause number gutter size to pop + if (normalNode != null) { + var node = gutterText.childNodes[normalNode - showingFrom]; + var minwidth = String(doc.size).length, val = eltText(node), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); + } + gutter.style.display = ""; + lineSpace.style.marginLeft = gutter.offsetWidth + "px"; + gutterDirty = false; + } + function updateSelection() { + var collapsed = posEq(sel.from, sel.to); + var fromPos = localCoords(sel.from, true); + var toPos = collapsed ? fromPos : localCoords(sel.to, true); + var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); + var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); + inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; + inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; + if (collapsed) { + cursor.style.top = headPos.y + "px"; + cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; + cursor.style.display = ""; + selectionDiv.style.display = "none"; + } else { + var sameLine = fromPos.y == toPos.y, html = ""; + function add(left, top, right, height) { + html += '
'; + } + var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; + var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; + if (sel.from.ch && fromPos.y >= 0) { + var right = sameLine ? clientWidth - toPos.x : 0; + add(fromPos.x, fromPos.y, right, th); + } + var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); + var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; + if (middleHeight > 0.2 * th) + add(0, middleStart, 0, middleHeight); + if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) + add(0, toPos.y, clientWidth - toPos.x, th); + selectionDiv.innerHTML = html; + cursor.style.display = "none"; + selectionDiv.style.display = ""; + } + } + + function setShift(val) { + if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); + else shiftSelecting = null; + } + function setSelectionUser(from, to) { + var sh = shiftSelecting && clipPos(shiftSelecting); + if (sh) { + if (posLess(sh, from)) from = sh; + else if (posLess(to, sh)) to = sh; + } + setSelection(from, to); + userSelChange = true; + } + // Update the selection. Last two args are only used by + // updateLines, since they have to be expressed in the line + // numbers before the update. + function setSelection(from, to, oldFrom, oldTo) { + goalColumn = null; + if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} + if (posEq(sel.from, from) && posEq(sel.to, to)) return; + if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} + + // Skip over hidden lines. + if (from.line != oldFrom) { + var from1 = skipHidden(from, oldFrom, sel.from.ch); + // If there is no non-hidden line left, force visibility on current line + if (!from1) setLineHidden(from.line, false); + else from = from1; + } + if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); + + if (posEq(from, to)) sel.inverted = false; + else if (posEq(from, sel.to)) sel.inverted = false; + else if (posEq(to, sel.from)) sel.inverted = true; + + if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { + var head = sel.inverted ? from : to; + if (head.line != sel.from.line && sel.from.line < doc.size) { + var oldLine = getLine(sel.from.line); + if (/^\s+$/.test(oldLine.text)) + setTimeout(operation(function() { + if (oldLine.parent && /^\s+$/.test(oldLine.text)) { + var no = lineNo(oldLine); + replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length}); + } + }, 10)); + } + } + + sel.from = from; sel.to = to; + selectionChanged = true; + } + function skipHidden(pos, oldLine, oldCh) { + function getNonHidden(dir) { + var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1; + while (lNo != end) { + var line = getLine(lNo); + if (!line.hidden) { + var ch = pos.ch; + if (ch > oldCh || ch > line.text.length) ch = line.text.length; + return {line: lNo, ch: ch}; + } + lNo += dir; + } + } + var line = getLine(pos.line); + if (!line.hidden) return pos; + if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); + else return getNonHidden(-1) || getNonHidden(1); + } + function setCursor(line, ch, user) { + var pos = clipPos({line: line, ch: ch || 0}); + (user ? setSelectionUser : setSelection)(pos, pos); + } + + function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));} + function clipPos(pos) { + if (pos.line < 0) return {line: 0, ch: 0}; + if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length}; + var ch = pos.ch, linelen = getLine(pos.line).text.length; + if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; + else if (ch < 0) return {line: pos.line, ch: 0}; + else return pos; + } + + function findPosH(dir, unit) { + var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch; + var lineObj = getLine(line); + function findNextLine() { + for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { + var lo = getLine(l); + if (!lo.hidden) { line = l; lineObj = lo; return true; } + } + } + function moveOnce(boundToLine) { + if (ch == (dir < 0 ? 0 : lineObj.text.length)) { + if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; + else return false; + } else if (lineObj.isWidgetBlock) { //Select the entire line + ch = dir < 0 ? 0 : lineObj.text.length; + } else ch += dir; + return true; + } + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word") { + var sawWord = false; + for (;;) { + if (dir < 0) if (!moveOnce()) break; + if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; + else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} + if (dir > 0) if (!moveOnce()) break; + } + } + return {line: line, ch: ch}; + } + function moveH(dir, unit) { + var pos = dir < 0 ? sel.from : sel.to; + if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit); + setCursor(pos.line, pos.ch, true); + } + function deleteH(dir, unit) { + if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); + else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); + else replaceRange("", sel.from, findPosH(dir, unit)); + userSelChange = true; + } + var goalColumn = null; + function moveV(dir, unit) { + var dist = 0, pos = sel.inverted ? sel.from : sel.to, loc = localCoords(pos, true); + if (goalColumn != null) pos.x = goalColumn; + if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); + else if (unit == "line") { + if (dir > 0) { + dist = Math.ceil(getLine(pos.line).height); + } else dist = 1; + dist *= textHeight(); + } + var target = coordsChar(loc.x, loc.y + dist * dir + 2); + if (unit == "page") scroller.scrollTop += localCoords(target, true).y - loc.y; + setCursor(target.line, target.ch, true); + goalColumn = loc.x; + } + + function selectWordAt(pos) { + var line = getLine(pos.line).text; + var start = pos.ch, end = pos.ch; + while (start > 0 && isWordChar(line.charAt(start - 1))) --start; + while (end < line.length && isWordChar(line.charAt(end))) ++end; + setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end}); + } + function selectLine(line) { + setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0})); + } + function indentSelected(mode) { + if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); + var e = sel.to.line - (sel.to.ch ? 0 : 1); + for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); + } + + function indentLine(n, how) { + if (!how) how = "add"; + if (how == "smart") { + if (!mode.indent) how = "prev"; + else var state = getStateBefore(n); + } + + var line = getLine(n), curSpace = line.indentation(options.tabSize), + curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "prev") { + if (n) indentation = getLine(n-1).indentation(options.tabSize); + else indentation = 0; + } + else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); + else if (how == "add") indentation = curSpace + options.indentUnit; + else if (how == "subtract") indentation = curSpace - options.indentUnit; + indentation = Math.max(0, indentation); + var diff = indentation - curSpace; + + if (!diff) { + if (sel.from.line != n && sel.to.line != n) return; + var indentString = curSpaceString; + } + else { + var indentString = "", pos = 0; + if (options.indentWithTabs) + for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} + while (pos < indentation) {++pos; indentString += " ";} + } + + replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); + } + + function loadMode() { + mode = CodeMirror.getMode(options, options.mode); + doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); + work = [0]; + startWorker(); + } + function gutterChanged() { + var visible = options.gutter || options.lineNumbers; + gutter.style.display = visible ? "" : "none"; + if (visible) gutterDirty = true; + else lineDiv.parentNode.style.marginLeft = 0; + } + function wrappingChanged(from, to) { + if (options.lineWrapping) { + wrapper.className += " CodeMirror-wrap"; + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(0, doc.size, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != 1) updateLineHeight(line, guess); + }); + lineSpace.style.width = code.style.width = ""; + } else { + wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); + maxWidth = null; maxLine = ""; + doc.iter(0, doc.size, function(line) { + if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); + if (line.text.length > maxLine.length) maxLine = line.text; + }); + } + changes.push({from: 0, to: doc.size}); + } + function makeTab(col) { + var w = options.tabSize - col % options.tabSize, cached = tabCache[w]; + if (cached) return cached; + for (var str = '', i = 0; i < w; ++i) str += " "; + return (tabCache[w] = {html: str + "", width: w}); + } + function themeChanged() { + scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") + + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + } + + function TextMarker() { this.set = []; } + TextMarker.prototype.clear = operation(function() { + var min = Infinity, max = -Infinity; + for (var i = 0, e = this.set.length; i < e; ++i) { + var line = this.set[i], mk = line.marked; + if (!mk || !line.parent) continue; + var lineN = lineNo(line); + min = Math.min(min, lineN); max = Math.max(max, lineN); + for (var j = 0; j < mk.length; ++j) + if (mk[j].marker == this) mk.splice(j--, 1); + } + if (min != Infinity) + changes.push({from: min, to: max + 1}); + }); + TextMarker.prototype.find = function() { + var from, to; + for (var i = 0, e = this.set.length; i < e; ++i) { + var line = this.set[i], mk = line.marked; + for (var j = 0; j < mk.length; ++j) { + var mark = mk[j]; + if (mark.marker == this) { + if (mark.from != null || mark.to != null) { + var found = lineNo(line); + if (found != null) { + if (mark.from != null) from = {line: found, ch: mark.from}; + if (mark.to != null) to = {line: found, ch: mark.to}; + } + } + } + } + } + return {from: from, to: to}; + }; + + function markText(from, to, className) { + from = clipPos(from); to = clipPos(to); + var tm = new TextMarker(); + if (!posLess(from, to)) return tm; + function add(line, from, to, className) { + getLine(line).addMark(new MarkedText(from, to, className, tm)); + } + if (from.line == to.line) add(from.line, from.ch, to.ch, className); + else { + add(from.line, from.ch, null, className); + for (var i = from.line + 1, e = to.line; i < e; ++i) + add(i, null, null, className); + add(to.line, null, to.ch, className); + } + changes.push({from: from.line, to: to.line + 1}); + return tm; + } + + function setBookmark(pos) { + pos = clipPos(pos); + var bm = new Bookmark(pos.ch); + getLine(pos.line).addMark(bm); + return bm; + } + + function findMarksAt(pos) { + pos = clipPos(pos); + var markers = [], marked = getLine(pos.line).marked; + if (!marked) return markers; + for (var i = 0, e = marked.length; i < e; ++i) { + var m = marked[i]; + if ((m.from == null || m.from <= pos.ch) && + (m.to == null || m.to >= pos.ch)) + markers.push(m.marker || m); + } + return markers; + } + + function addGutterMarker(line, text, className) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = {text: text, style: className}; + gutterDirty = true; + return line; + } + function removeGutterMarker(line) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = null; + gutterDirty = true; + } + + function changeLine(handle, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(clipLine(handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no)) changes.push({from: no, to: no + 1}); + else return null; + return line; + } + function setLineClass(handle, className, bgClassName) { + return changeLine(handle, function(line) { + if (line.className != className || line.bgClassName != bgClassName) { + line.className = className; + line.bgClassName = bgClassName; + return true; + } + }); + } + function setLineHidden(handle, hidden) { + return changeLine(handle, function(line, no) { + if (line.hidden != hidden) { + line.hidden = hidden; + updateLineHeight(line, hidden ? 0 : 1); + var fline = sel.from.line, tline = sel.to.line; + if (hidden && (fline == no || tline == no)) { + var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; + var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; + // Can't hide the last visible line, we'd have no place to put the cursor + if (!to) return; + setSelection(from, to); + } + return (gutterDirty = true); + } + }); + } + + function lineInfo(line) { + if (typeof line == "number") { + if (!isLine(line)) return null; + var n = line; + line = getLine(line); + if (!line) return null; + } + else { + var n = lineNo(line); + if (n == null) return null; + } + var marker = line.gutterMarker; + return {line: n, handle: line, text: line.text, markerText: marker && marker.text, + markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; + } + + function stringWidth(str) { + measure.innerHTML = "
x
"; + measure.firstChild.firstChild.firstChild.nodeValue = str; + return measure.firstChild.firstChild.offsetWidth || 10; + } + // These are used to go from pixel positions to character + // positions, taking varying character widths into account. + function charFromX(line, x) { + if (x <= 0) return 0; + var lineObj = getLine(line), text = lineObj.text; + function getX(len) { + measure.innerHTML = "
" + lineObj.getHTML(makeTab, len) + "
"; + return measure.firstChild.firstChild.offsetWidth; + } + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil(x / charWidth())); + for (;;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else {toX = estX; to = estimated; break;} + } + if (x > toX) return to; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); estX = getX(estimated); + if (estX < x) {from = estimated; fromX = estX;} + // Do a binary search between these bounds. + for (;;) { + if (to - from <= 1) return (toX - x > x - fromX) ? from : to; + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX;} + else {from = middle; fromX = middleX;} + } + } + + var tempId = Math.floor(Math.random() * 0xffffff).toString(16); + function measureLine(line, ch) { + if (ch == 0) return {top: 0, left: 0}; + var extra = ""; + // Include extra text at the end to make sure the measured line is wrapped in the right way. + if (options.lineWrapping) { + var end = line.text.indexOf(" ", ch + 6); + extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0))); + } + measure.innerHTML = "
" + line.getHTML(makeTab, ch) +
+        '' + htmlEscape(line.text.charAt(ch) || " ") + "" +
+        extra + "
"; + var elt = document.getElementById("CodeMirror-temp-" + tempId); + var top = elt.offsetTop, left = elt.offsetLeft; + // Older IEs report zero offsets for spans directly after a wrap + if (ie && top == 0 && left == 0) { + var backup = document.createElement("span"); + backup.innerHTML = "x"; + elt.parentNode.insertBefore(backup, elt.nextSibling); + top = backup.offsetTop; + } + return {top: top, left: left}; + } + function localCoords(pos, inLineWrap) { + var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0)); + if (pos.ch == 0) x = 0; + else { + var sp = measureLine(getLine(pos.line), pos.ch); + x = sp.left; + if (options.lineWrapping) y += Math.max(0, sp.top); + } + return {x: x, y: y, yBot: y + lh}; + } + // Coords must be lineSpace-local + function coordsChar(x, y) { + if (y < 0) y = 0; + var th = textHeight(), cw = charWidth(), heightPos = displayOffset + y / th; + var lineNo = lineAtHeight(doc, heightPos); + if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; + var lineObj = getLine(lineNo), text = lineObj.text; + var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; + if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; + function getX(len) { + var sp = measureLine(lineObj, len); + if (tw) { + var off = Math.round(sp.top / th); + return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); + } + return sp.left; + } + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw)); + for (;;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else {toX = estX; to = estimated; break;} + } + if (x > toX) return {line: lineNo, ch: to}; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); estX = getX(estimated); + if (estX < x) {from = estimated; fromX = estX;} + // Do a binary search between these bounds. + for (;;) { + if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to}; + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX;} + else {from = middle; fromX = middleX;} + } + } + function pageCoords(pos) { + var local = localCoords(pos, true), off = eltOffset(lineSpace); + return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; + } + + var cachedHeight, cachedHeightFor, measureText; + function textHeight() { + if (measureText == null) { + measureText = "
";
+        for (var i = 0; i < 49; ++i) measureText += "x
"; + measureText += "x
"; + } + var offsetHeight = lineDiv.clientHeight; + if (offsetHeight == cachedHeightFor) return cachedHeight; + cachedHeightFor = offsetHeight; + measure.innerHTML = measureText; + cachedHeight = measure.firstChild.offsetHeight / 50 || 1; + measure.innerHTML = ""; + return cachedHeight; + } + var cachedWidth, cachedWidthFor = 0; + function charWidth() { + if (scroller.clientWidth == cachedWidthFor) return cachedWidth; + cachedWidthFor = scroller.clientWidth; + return (cachedWidth = stringWidth("x")); + } + function paddingTop() {return lineSpace.offsetTop;} + function paddingLeft() {return lineSpace.offsetLeft;} + + function posFromMouse(e, liberal) { + var offW = eltOffset(scroller, true), x, y; + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX; y = e.clientY; } catch (e) { return null; } + // This is a mess of a heuristic to try and determine whether a + // scroll-bar was clicked or not, and to return null if one was + // (and !liberal). + if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) + return null; + var offL = eltOffset(lineSpace, true); + return coordsChar(x - offL.left, y - offL.top); + } + function onContextMenu(e) { + var pos = posFromMouse(e), scrollPos = scroller.scrollTop; + if (!pos || window.opera) return; // Opera is difficult. + if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) + operation(setCursor)(pos.line, pos.ch); + + var oldCSS = input.style.cssText; + inputDiv.style.position = "absolute"; + input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + leaveInputAlone = true; + var val = input.value = getSelection(); + focusInput(); + selectInput(input); + function rehide() { + var newVal = splitLines(input.value).join("\n"); + if (newVal != val) operation(replaceSelection)(newVal, "end"); + inputDiv.style.position = "relative"; + input.style.cssText = oldCSS; + if (ie_lt9) scroller.scrollTop = scrollPos; + leaveInputAlone = false; + resetInput(true); + slowPoll(); + } + + if (gecko) { + e_stop(e); + var mouseup = connect(window, "mouseup", function() { + mouseup(); + setTimeout(rehide, 20); + }, true); + } else { + setTimeout(rehide, 50); + } + } + + // Cursor-blinking + function restartBlink() { + clearInterval(blinker); + var on = true; + cursor.style.visibility = ""; + blinker = setInterval(function() { + cursor.style.visibility = (on = !on) ? "" : "hidden"; + }, 650); + } + + var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; + function matchBrackets(autoclear) { + var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1; + var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; + if (!match) return; + var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; + for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2) + if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;} + + var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; + function scan(line, from, to) { + if (!line.text) return; + var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; + for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { + var text = st[i]; + if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;} + for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { + if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { + var match = matching[cur]; + if (match.charAt(1) == ">" == forward) stack.push(cur); + else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; + else if (!stack.length) return {pos: pos, match: true}; + } + } + } + } + for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) { + var line = getLine(i), first = i == head.line; + var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); + if (found) break; + } + if (!found) found = {pos: null, match: false}; + var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; + var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), + two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style); + var clear = operation(function(){one.clear(); two && two.clear();}); + if (autoclear) setTimeout(clear, 800); + else bracketHighlighted = clear; + } + + // 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(n) { + var minindent, minline; + for (var search = n, lim = n - 40; search > lim; --search) { + if (search == 0) return 0; + var line = getLine(search-1); + if (line.stateAfter) return search; + var indented = line.indentation(options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + function getStateBefore(n) { + var start = findStartLine(n), state = start && getLine(start-1).stateAfter; + if (!state) state = startState(mode); + else state = copyState(mode, state); + doc.iter(start, n, function(line) { + line.highlight(mode, state, options.tabSize); + line.stateAfter = copyState(mode, state); + }); + if (start < n) changes.push({from: start, to: n}); + if (n < doc.size && !getLine(n).stateAfter) work.push(n); + return state; + } + function highlightLines(start, end) { + var state = getStateBefore(start); + doc.iter(start, end, function(line) { + line.highlight(mode, state, options.tabSize); + line.stateAfter = copyState(mode, state); + }); + } + function highlightWorker() { + var end = +new Date + options.workTime; + var foundWork = work.length; + while (work.length) { + if (!getLine(showingFrom).stateAfter) var task = showingFrom; + else var task = work.pop(); + if (task >= doc.size) continue; + var start = findStartLine(task), state = start && getLine(start-1).stateAfter; + if (state) state = copyState(mode, state); + else state = startState(mode); + + var unchanged = 0, compare = mode.compareStates, realChange = false, + i = start, bail = false; + doc.iter(i, doc.size, function(line) { + var hadState = line.stateAfter; + if (+new Date > end) { + work.push(i); + startWorker(options.workDelay); + if (realChange) changes.push({from: task, to: i + 1}); + return (bail = true); + } + var changed = line.highlight(mode, state, options.tabSize); + if (changed) realChange = true; + line.stateAfter = copyState(mode, state); + if (compare) { + if (hadState && compare(hadState, state)) return true; + } else { + if (changed !== false || !hadState) unchanged = 0; + else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, ""))) + return true; + } + ++i; + }); + if (bail) return; + if (realChange) changes.push({from: task, to: i + 1}); + } + if (foundWork && options.onHighlightComplete) + options.onHighlightComplete(instance); + } + function startWorker(time) { + if (!work.length) return; + highlight.set(time, operation(highlightWorker)); + } + + // 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. + function startOperation() { + updateInput = userSelChange = textChanged = null; + changes = []; selectionChanged = false; callbacks = []; + } + function endOperation() { + var reScroll = false, updated; + if (selectionChanged) reScroll = !scrollCursorIntoView(); + if (changes.length) updated = updateDisplay(changes, true); + else { + if (selectionChanged) updateSelection(); + if (gutterDirty) updateGutter(); + } + if (reScroll) scrollCursorIntoView(); + if (selectionChanged) {scrollEditorIntoView(); restartBlink();} + + if (focused && !leaveInputAlone && + (updateInput === true || (updateInput !== false && selectionChanged))) + resetInput(userSelChange); + + if (selectionChanged && options.matchBrackets) + setTimeout(operation(function() { + if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} + if (posEq(sel.from, sel.to)) matchBrackets(false); + }), 20); + var tc = textChanged, cbs = callbacks; // these can be reset by callbacks + if (selectionChanged && options.onCursorActivity) + options.onCursorActivity(instance); + if (tc && options.onChange && instance) + options.onChange(instance, tc); + for (var i = 0; i < cbs.length; ++i) cbs[i](instance); + if (updated && options.onUpdate) options.onUpdate(instance); + } + var nestedOperation = 0; + function operation(f) { + return function() { + if (!nestedOperation++) startOperation(); + try {var result = f.apply(this, arguments);} + finally {if (!--nestedOperation) endOperation();} + return result; + }; + } + + for (var ext in extensions) + if (extensions.propertyIsEnumerable(ext) && + !instance.propertyIsEnumerable(ext)) + instance[ext] = extensions[ext]; + return instance; + } // (end of function CodeMirror) + + // The default configuration options. + CodeMirror.defaults = { + value: "", + mode: null, + theme: "default", + indentUnit: 2, + indentWithTabs: false, + smartIndent: true, + tabSize: 4, + keyMap: "default", + extraKeys: null, + electricChars: true, + autoClearEmptyLines: false, + onKeyEvent: null, + lineWrapping: false, + lineNumbers: false, + gutter: false, + fixedGutter: false, + firstLineNumber: 1, + readOnly: false, + onChange: null, + onCursorActivity: null, + onGutterClick: null, + onHighlightComplete: null, + onUpdate: null, + onFocus: null, onBlur: null, onScroll: null, + matchBrackets: false, + workTime: 100, + workDelay: 200, + pollInterval: 100, + undoDepth: 40, + tabindex: null, + autofocus: null + }; + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var win = /Win/.test(navigator.platform); + + // 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; + 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 (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) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) { + if (window.console) console.warn("No mode " + spec.name + " found, falling back to plain text."); + return CodeMirror.getMode(options, "text/plain"); + } + return mfactory(options, spec); + }; + CodeMirror.listModes = function() { + var list = []; + for (var m in modes) + if (modes.propertyIsEnumerable(m)) list.push(m); + return list; + }; + CodeMirror.listMIMEs = function() { + var list = []; + for (var m in mimeModes) + if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]}); + return list; + }; + + var extensions = CodeMirror.extensions = {}; + CodeMirror.defineExtension = function(name, func) { + extensions[name] = func; + }; + + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, + 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, {line: from.line + 1, ch: 0}); + else cm.replaceRange("", from, sel ? to : {line: from.line}); + }, + deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});}, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + goDocStart: function(cm) {cm.setCursor(0, 0, true);}, + goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);}, + goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);}, + goLineStartSmart: function(cm) { + var cur = cm.getCursor(); + var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/)); + cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true); + }, + goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);}, + 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");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharLeft: function(cm) {cm.deleteH(-1, "char");}, + delCharRight: function(cm) {cm.deleteH(1, "char");}, + delWordLeft: function(cm) {cm.deleteH(-1, "word");}, + delWordRight: function(cm) {cm.deleteH(1, "word");}, + 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");}, + 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), + {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); + }, + newlineAndIndent: function(cm) { + cm.replaceSelection("\n", "end"); + cm.indentLine(cm.getCursor().line); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "insertTab", "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": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "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": "goWordLeft", + "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", + "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "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", + 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": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft", + "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + function lookupKey(name, extraMap, map, handle) { + function lookup(map) { + map = getKeyMap(map); + var found = map[name]; + if (found != null && handle(found)) return true; + if (map.catchall) return handle(map.catchall); + 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) { + if (lookup(fallthrough[i])) return true; + } + return false; + } + if (extraMap && lookup(extraMap)) return true; + return lookup(map); + } + function isModifierKey(event) { + var name = keyNames[e_prop(event, "keyCode")]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + } + + CodeMirror.fromTextArea = function(textarea, options) { + if (!options) options = {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabindex) + options.tabindex = textarea.tabindex; + if (options.autofocus == null && textarea.getAttribute("autofocus") != null) + options.autofocus = true; + + function save() {textarea.value = instance.getValue();} + if (textarea.form) { + // Deplorable hack to make the submit method do the right thing. + var rmSubmit = connect(textarea.form, "submit", save, true); + if (typeof textarea.form.submit == "function") { + var realSubmit = textarea.form.submit; + function wrappedSubmit() { + save(); + textarea.form.submit = realSubmit; + textarea.form.submit(); + textarea.form.submit = wrappedSubmit; + } + textarea.form.submit = wrappedSubmit; + } + } + + textarea.style.display = "none"; + var instance = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + instance.save = save; + instance.getTextArea = function() { return textarea; }; + instance.toTextArea = function() { + save(); + textarea.parentNode.removeChild(instance.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + rmSubmit(); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + return instance; + }; + + // 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; + + // 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; + } + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == 0;}, + peek: function() {return this.string.charAt(this.pos);}, + 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() {return countColumn(this.string, this.start, this.tabSize);}, + indentation: function() {return countColumn(this.string, null, this.tabSize);}, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} + if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } + else { + var match = this.string.slice(this.pos).match(pattern); + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);} + }; + CodeMirror.StringStream = StringStream; + + function MarkedText(from, to, className, marker) { + this.from = from; this.to = to; this.style = className; this.marker = marker; + } + MarkedText.prototype = { + attach: function(line) { this.marker.set.push(line); }, + detach: function(line) { + var ix = indexOf(this.marker.set, line); + if (ix > -1) this.marker.set.splice(ix, 1); + }, + split: function(pos, lenBefore) { + if (this.to <= pos && this.to != null) return null; + var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore; + var to = this.to == null ? null : this.to - pos + lenBefore; + return new MarkedText(from, to, this.style, this.marker); + }, + dup: function() { return new MarkedText(null, null, this.style, this.marker); }, + clipTo: function(fromOpen, from, toOpen, to, diff) { + if (fromOpen && to > this.from && (to < this.to || this.to == null)) + this.from = null; + else if (this.from != null && this.from >= from) + this.from = Math.max(to, this.from) + diff; + if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null)) + this.to = null; + else if (this.to != null && this.to > from) + this.to = to < this.to ? this.to + diff : from; + }, + isDead: function() { return this.from != null && this.to != null && this.from >= this.to; }, + sameSet: function(x) { return this.marker == x.marker; } + }; + + function Bookmark(pos) { + this.from = pos; this.to = pos; this.line = null; + } + Bookmark.prototype = { + attach: function(line) { this.line = line; }, + detach: function(line) { if (this.line == line) this.line = null; }, + split: function(pos, lenBefore) { + if (pos < this.from) { + this.from = this.to = (this.from - pos) + lenBefore; + return this; + } + }, + isDead: function() { return this.from > this.to; }, + clipTo: function(fromOpen, from, toOpen, to, diff) { + if ((fromOpen || from < this.from) && (toOpen || to > this.to)) { + this.from = 0; this.to = -1; + } else if (this.from > from) { + this.from = this.to = Math.max(to, this.from) + diff; + } + }, + sameSet: function(x) { return false; }, + find: function() { + if (!this.line || !this.line.parent) return null; + return {line: lineNo(this.line), ch: this.from}; + }, + clear: function() { + if (this.line) { + var found = indexOf(this.line.marked, this); + if (found != -1) this.line.marked.splice(found, 1); + this.line = null; + } + } + }; + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + function Line(text, styles) { + this.styles = styles || [text, null]; + this.text = text; + this.height = 1; + this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null; + this.stateAfter = this.parent = this.hidden = null; + this.isWidgetBlock = false; + } + Line.inheritMarks = function(text, orig) { + var ln = new Line(text), mk = orig && orig.marked; + if (mk) { + for (var i = 0; i < mk.length; ++i) { + if (mk[i].to == null && mk[i].style) { + var newmk = ln.marked || (ln.marked = []), mark = mk[i]; + var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln); + } + } + } + return ln; + } + Line.prototype = { + // Replace a piece of a line, keeping the styles around it intact. + replace: function(from, to_, text) { + var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_; + copyStyles(0, from, this.styles, st); + if (text) st.push(text, null); + copyStyles(to, this.text.length, this.styles, st); + this.styles = st; + this.text = this.text.slice(0, from) + text + this.text.slice(to); + this.stateAfter = null; + if (mk) { + var diff = text.length - (to - from); + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + mark.clipTo(from == null, from || 0, to_ == null, to, diff); + if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);} + } + } + }, + // Split a part off a line, keeping styles and markers intact. + split: function(pos, textBefore) { + var st = [textBefore, null], mk = this.marked; + copyStyles(pos, this.text.length, this.styles, st); + var taken = new Line(textBefore + this.text.slice(pos), st); + if (mk) { + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + var newmark = mark.split(pos, textBefore.length); + if (newmark) { + if (!taken.marked) taken.marked = []; + taken.marked.push(newmark); newmark.attach(taken); + if (newmark == mark) mk.splice(i--, 1); + } + } + } + return taken; + }, + append: function(line) { + var mylen = this.text.length, mk = line.marked, mymk = this.marked; + this.text += line.text; + copyStyles(0, line.text.length, line.styles, this.styles); + if (mymk) { + for (var i = 0; i < mymk.length; ++i) + if (mymk[i].to == null) mymk[i].to = mylen; + } + if (mk && mk.length) { + if (!mymk) this.marked = mymk = []; + outer: for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + if (!mark.from) { + for (var j = 0; j < mymk.length; ++j) { + var mymark = mymk[j]; + if (mymark.to == mylen && mymark.sameSet(mark)) { + mymark.to = mark.to == null ? null : mark.to + mylen; + if (mymark.isDead()) { + mymark.detach(this); + mk.splice(i--, 1); + } + continue outer; + } + } + } + mymk.push(mark); + mark.attach(this); + mark.from += mylen; + if (mark.to != null) mark.to += mylen; + } + } + }, + fixMarkEnds: function(other) { + var mk = this.marked, omk = other.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i], close = mark.to == null; + if (close && omk) { + for (var j = 0; j < omk.length; ++j) + if (omk[j].sameSet(mark)) {close = false; break;} + } + if (close) mark.to = this.text.length; + } + }, + fixMarkStarts: function() { + var mk = this.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) + if (mk[i].from == null) mk[i].from = 0; + }, + addMark: function(mark) { + mark.attach(this); + if (this.marked == null) this.marked = []; + this.marked.push(mark); + this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);}); + }, + // Run the given mode's parser over a line, update the styles + // array, which contains alternating fragments of text and CSS + // classes. + highlight: function(mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0; + var changed = false, curWord = st[0], prevWord; + if (this.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol()) { + var style = mode.token(stream, state); + var substr = this.text.slice(stream.start, stream.pos); + stream.start = stream.pos; + if (pos && st[pos-1] == style) + st[pos-2] += substr; + else if (substr) { + if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true; + st[pos++] = substr; st[pos++] = style; + prevWord = curWord; curWord = st[pos]; + } + // Give up when line is ridiculously long + if (stream.pos > 5000) { + st[pos++] = this.text.slice(stream.pos); st[pos++] = null; + break; + } + } + if (st.length != pos) {st.length = pos; changed = true;} + if (pos && st[pos-2] != prevWord) changed = true; + this.isWidgetBlock = (st.length == 2 && st[1] && typeof st[1] == 'object'); + // Short lines with simple highlights return null, and are + // counted as changed by the driver because they are likely to + // highlight the same way in various contexts. + return changed || (st.length < 5 && this.text.length < 10 ? null : false); + }, + nodeAdded: function(node) { + if (this.isWidgetBlock) this.styles[1].callback(node, this); + //this.setHeight(node.clientHeight); + }, + setHeight: function(newHeight) { + if (this.isWidgetBlock) newHeight = 300 / 14.0; + this.growHeight(newHeight - this.height); + }, + //This is a bottom-up height change. InsertHeight is a top-down height change + growHeight: function(deltaHeight) { + if (this.parent && deltaHeight != 0) { + this.parent.growHeight(deltaHeight); + } + this.height = this.height + deltaHeight; + }, + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(mode, state, ch) { + var txt = this.text, stream = new StringStream(txt); + while (stream.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, + state: state}; + }, + indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, + // Produces an HTML fragment for the line, taking selection, + // marking, and highlighting into account. + getHTML: function(makeTab, endAt) { + var html = [], first = true, col = 0; + function span(text, style) { + if (!text) return; + // Work around a bug where, in some compat modes, IE ignores leading spaces + if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); + first = false; + if (text.indexOf("\t") == -1) { + col += text.length; + var escaped = htmlEscape(text); + } else { + var escaped = ""; + for (var pos = 0;;) { + var idx = text.indexOf("\t", pos); + if (idx == -1) { + escaped += htmlEscape(text.slice(pos)); + col += text.length - pos; + break; + } else { + col += idx - pos; + var tab = makeTab(col); + escaped += htmlEscape(text.slice(pos, idx)) + tab.html; + col += tab.width; + pos = idx + 1; + } + } + } + if (style) html.push('', escaped, ""); + else html.push(escaped); + } + var st = this.styles, allText = this.text, marked = this.marked; + var len = allText.length; + if (this.isWidgetBlock) return st[1].creator(allText); + if (endAt != null) len = Math.min(endAt, len); + function styleToClass(style) { + if (!style) return null; + return "cm-" + style.replace(/ +/g, " cm-"); + } + if (!allText && endAt == null) + span(" "); + else if (!marked || !marked.length) + for (var i = 0, ch = 0; ch < len; i+=2) { + var str = st[i], style = st[i+1], l = str.length; + if (ch + l > len) str = str.slice(0, len - ch); + ch += l; + span(str, styleToClass(style)); + } + else { + var pos = 0, i = 0, text = "", style, sg = 0; + var nextChange = marked[0].from || 0, marks = [], markpos = 0; + function advanceMarks() { + var m; + while (markpos < marked.length && + ((m = marked[markpos]).from == pos || m.from == null)) { + if (m.style != null) marks.push(m); + ++markpos; + } + nextChange = markpos < marked.length ? marked[markpos].from : Infinity; + for (var i = 0; i < marks.length; ++i) { + var to = marks[i].to || Infinity; + if (to == pos) marks.splice(i--, 1); + else nextChange = Math.min(to, nextChange); + } + } + var m = 0; + while (pos < len) { + if (nextChange == pos) advanceMarks(); + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + var appliedStyle = style; + for (var j = 0; j < marks.length; ++j) + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style; + span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + } + text = st[i++]; style = styleToClass(st[i++]); + } + } + } + return html.join(""); + }, + cleanUp: function() { + this.parent = null; + if (this.marked) + for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this); + } + }; + // Utility used by replace and split above + function copyStyles(from, to, source, dest) { + for (var i = 0, pos = 0, state = 0; pos < to; i+=2) { + var part = source[i], end = pos + part.length; + if (state == 0) { + if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]); + if (end >= from) state = 1; + } + else if (state == 1) { + if (end > to) dest.push(part.slice(0, to - pos), source[i+1]); + else dest.push(part, source[i+1]); + } + pos = end; + } + } + + // Data structure that holds the sequence of lines. + 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; }, + remove: function(at, n, callbacks) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + line.cleanUp(); + if (line.handlers) + for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]); + } + this.lines.splice(at, n); + }, + collapse: function(lines) { + lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); + }, + //This is a bottom-up height change. InsertHeight is a top-down height change + growHeight: function(deltaHeight) { + if (this.parent && deltaHeight != 0) { + this.parent.growHeight(deltaHeight) + } + this.height = this.height + deltaHeight; + }, + insertHeight: function(at, lines, height) { + this.height += height; + this.lines.splice.apply(this.lines, [at, 0].concat(lines)); + 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; }, + remove: function(at, n, callbacks) { + 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.remove(at, rm, callbacks); + 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); + }, + insert: function(at, lines) { + var height = 0; + for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; + this.insertHeight(at, lines, height); + }, + insertHeight: 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.insertHeight(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; + } + }, + //This is a bottom-up height change. InsertHeight is a top-down height change + growHeight: function(deltaHeight) { + if (this.parent && deltaHeight != 0) { + this.parent.growHeight(deltaHeight) + } + this.height = this.height + deltaHeight; + }, + 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(); + }, + iter: function(from, to, op) { this.iterN(from, to - from, op); }, + 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; + } + } + }; + + function getLineAt(chunk, n) { + 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 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, e = chunk.children.length; ; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no; + } + function lineAtHeight(chunk, h) { + var n = 0; + 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(chunk, n) { + var h = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; continue outer; } + n -= sz; + h += child.height; + } + return h; + } while (!chunk.lines); + for (var i = 0; i < n; ++i) h += chunk.lines[i].height; + return h; + } + + // The history object 'chunks' changes that are made close together + // and at almost the same time into bigger undoable units. + function History() { + this.time = 0; + this.done = []; this.undone = []; + } + History.prototype = { + addChange: function(start, added, old) { + this.undone.length = 0; + var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; + var dtime = time - this.time; + if (dtime > 400 || !last) { + this.done.push([{start: start, added: added, old: old}]); + } else if (last.start > start + old.length || last.start + last.added < start - last.added + last.old.length) { + cur.push({start: start, added: added, old: old}); + } else { + var oldoff = 0; + if (start < last.start) { + for (var i = last.start - start - 1; i >= 0; --i) + last.old.unshift(old[i]); + oldoff = Math.min(0, added - old.length); + last.added += last.start - start + oldoff; + last.start = start; + } else if (last.start < start) { + oldoff = start - last.start; + added += oldoff; + } + for (var i = last.added - oldoff, e = old.length; i < e; ++i) + last.old.push(old[i]); + if (last.added < added) last.added = added; + } + this.time = time; + } + }; + + 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_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) { + if (e.which) return e.which; + else if (e.button & 1) return 1; + else if (e.button & 2) return 3; + else if (e.button & 4) return 2; + } + + // Allow 3rd-party code to override event properties by adding an override + // object to an event object. + function e_prop(e, prop) { + var overridden = e.override && e.override.hasOwnProperty(prop); + return overridden ? e.override[prop] : e[prop]; + } + + // Event handler registration. If disconnect is true, it'll return a + // function that unregisters the handler. + function connect(node, type, handler, disconnect) { + if (typeof node.addEventListener == "function") { + node.addEventListener(type, handler, false); + if (disconnect) return function() {node.removeEventListener(type, handler, false);}; + } + else { + var wrapHandler = function(event) {handler(event || window.event);}; + node.attachEvent("on" + type, wrapHandler); + if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; + } + } + CodeMirror.connect = connect; + + function Delayed() {this.id = null;} + Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; + + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); + var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); + var webkit = /WebKit\//.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var khtml = /KHTML\//.test(navigator.userAgent); + + // 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 = document.createElement('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + var lineSep = "\n"; + // Feature-detect whether newlines in textareas are converted to \r\n + (function () { + var te = document.createElement("textarea"); + te.value = "foo\nbar"; + if (te.value.indexOf("\r") > -1) lineSep = "\r\n"; + }()); + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countColumn(string, end, tabSize) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = 0, n = 0; i < end; ++i) { + if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); + else ++n; + } + return n; + } + + function computedStyle(elt) { + if (elt.currentStyle) return elt.currentStyle; + return window.getComputedStyle(elt, null); + } + + // Find the position of an element by following the offsetParent chain. + // If screen==true, it returns screen (rather than page) coordinates. + function eltOffset(node, screen) { + var bod = node.ownerDocument.body; + var x = 0, y = 0, skipBody = false; + for (var n = node; n; n = n.offsetParent) { + var ol = n.offsetLeft, ot = n.offsetTop; + // Firefox reports weird inverted offsets when the body has a border. + if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); } + else { x += ol, y += ot; } + if (screen && computedStyle(n).position == "fixed") + skipBody = true; + } + var e = screen && !skipBody ? null : bod; + for (var n = node.parentNode; n != e; n = n.parentNode) + if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;} + return {left: x, top: y}; + } + // Use the faster and saner getBoundingClientRect method when possible. + if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) { + // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, + // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) + try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } + catch(e) { box = {top: 0, left: 0}; } + if (!screen) { + // Get the toplevel scroll, working around browser differences. + if (window.pageYOffset == null) { + var t = document.documentElement || document.body.parentNode; + if (t.scrollTop == null) t = document.body; + box.top += t.scrollTop; box.left += t.scrollLeft; + } else { + box.top += window.pageYOffset; box.left += window.pageXOffset; + } + } + return box; + }; + + // Get a node's text content. + function eltText(node) { + return node.textContent || node.innerText || node.nodeValue || ""; + } + function selectInput(node) { + if (ios) { // Mobile Safari apparently has a bug where select() is broken. + node.selectionStart = 0; + node.selectionEnd = node.value.length; + } else node.select(); + } + + // Operations on {line, ch} objects. + 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 {line: x.line, ch: x.ch};} + + var escapeElement = document.createElement("pre"); + function htmlEscape(str) { + escapeElement.textContent = str; + return escapeElement.innerHTML; + } + // Recent (late 2011) Opera betas insert bogus newlines at the start + // of the textContent, so we strip those. + if (htmlEscape("a") == "\na") + htmlEscape = function(str) { + escapeElement.textContent = str; + return escapeElement.innerHTML.slice(1); + }; + // Some IEs don't preserve tabs through innerHTML + else if (htmlEscape("\t") != "\t") + htmlEscape = function(str) { + escapeElement.innerHTML = ""; + escapeElement.appendChild(document.createTextNode(str)); + return escapeElement.innerHTML; + }; + CodeMirror.htmlEscape = htmlEscape; + + // Used to position the cursor after an undo/redo by finding the + // last edited character. + function editEnd(from, to) { + if (!to) return 0; + if (!from) return to.length; + for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) + if (from.charAt(i) != to.charAt(j)) break; + return j + 1; + } + + 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 isWordChar(ch) { + return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase(); + } + + // 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, nl, result = []; + while ((nl = string.indexOf("\n", pos)) > -1) { + result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); + pos = nl + 1; + } + result.push(string.slice(pos)); + return result; + } : function(string){return string.split(/\r?\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; + }; + + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + 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", 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; + })(); + + return CodeMirror; +})(); diff --git a/static/js/CodeMirror/mitx_markdown.js b/static/js/CodeMirror/mitx_markdown.js new file mode 100644 index 0000000000..02d6e609c2 --- /dev/null +++ b/static/js/CodeMirror/mitx_markdown.js @@ -0,0 +1,345 @@ +$(function(){ + $(document).ready(function() { + $("a[rel*=leanModal]").leanModal(); + + $("body").append('
'); + + //This is the editor that pops up as a modal + var editorCircuit = $("#schematic_editor").get(0); + //This is the circuit that they last clicked. The one being edited. + var editingCircuit = null; + //Notice we use live, because new circuits can be inserted + $(".schematic_open").live("click", function() { + //Find the new editingCircuit. Transfer its contents to the editorCircuit + editingCircuit = $(this).children("input.schematic").get(0); + + editingCircuit.schematic.update_value(); + var circuit_so_far = $(editingCircuit).val(); + + var n = editorCircuit.schematic.components.length; + for (var i = 0; i < n; i++) + editorCircuit.schematic.components[n - 1 - i].remove(); + + editorCircuit.schematic.load_schematic(circuit_so_far, ""); + }); + + $("#circuit_save_btn").click(function () { + //Take the circuit from the editor and put it back into editingCircuit + editorCircuit.schematic.update_value(); + var saving_circuit = $(editorCircuit).val(); + + var n = editingCircuit.schematic.components.length; + for (var i = 0; i < n; i++) + editingCircuit.schematic.components[n - 1 - i].remove(); + + editingCircuit.schematic.load_schematic(saving_circuit, ""); + + if (editingCircuit.codeMirrorLine) { + editingCircuit.codeMirrorLine.text = "circuit-schematic:" + saving_circuit; + } + + $(".modal_close").first().click(); + }); + }); +}); + +CodeMirror.defineMode("mitx_markdown", function(cmCfg, modeCfg) { + + var htmlMode = CodeMirror.getMode(cmCfg, { name: 'xml', htmlMode: true }); + + var header = 'header' + , code = 'comment' + , quote = 'quote' + , list = 'string' + , hr = 'hr' + , linktext = 'link' + , linkhref = 'string' + , em = 'em' + , strong = 'strong' + , emstrong = 'emstrong'; + + var circuit_formatter = { + creator: function(text) { + var circuit_value = text.match(circuitRE)[1] + + //TODO: We need real html escaping here + circuit_value = CodeMirror.htmlEscape(circuit_value);// circuit_value.replace("\"", "'"); + + var html = ""; + + return html; + }, + callback: function(node, line) { + update_schematics(); + var schmInput = node.firstChild; + schmInput.codeMirrorLine = line; + $(node).leanModal(); + } + }; + + var hrRE = /^[*-=_]/ + , ulRE = /^[*-+]\s+/ + , olRE = /^[0-9]+\.\s+/ + , headerRE = /^(?:\={3,}|-{3,})$/ + , codeRE = /^(k:\t|\s{4,})/ + , textRE = /^[^\[*_\\<>`]+/ + , circuitRE = /^circuit-schematic:(.*)$/; + + function switchInline(stream, state, f) { + state.f = state.inline = f; + return f(stream, state); + } + + function switchBlock(stream, state, f) { + state.f = state.block = f; + return f(stream, state); + } + + + // Blocks + + function blockNormal(stream, state) { + var match; + if (stream.match(circuitRE)) { + stream.skipToEnd(); + return circuit_formatter; + } else if (stream.match(codeRE)) { + stream.skipToEnd(); + return code; + } else if (stream.eatSpace()) { + return null; + } else if (stream.peek() === '#' || stream.match(headerRE)) { + state.header = true; + } else if (stream.eat('>')) { + state.indentation++; + state.quote = true; + } else if (stream.peek() === '[') { + return switchInline(stream, state, footnoteLink); + } else if (hrRE.test(stream.peek())) { + var re = new RegExp('(?:\s*['+stream.peek()+']){3,}$'); + if (stream.match(re, true)) { + return hr; + } + } else if (match = stream.match(ulRE, true) || stream.match(olRE, true)) { + state.indentation += match[0].length; + return list; + } + + return switchInline(stream, state, state.inline); + } + + function htmlBlock(stream, state) { + var style = htmlMode.token(stream, state.htmlState); + if (style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) { + state.f = inlineNormal; + state.block = blockNormal; + } + return style; + } + + + // Inline + function getType(state) { + + // Set defaults + returnValue = ''; + + // Strong / Emphasis + if(state.strong){ + if(state.em){ + returnValue += (returnValue ? ' ' : '') + emstrong; + } else { + returnValue += (returnValue ? ' ' : '') + strong; + } + } else { + if(state.em){ + returnValue += (returnValue ? ' ' : '') + em; + } + } + + // Header + if(state.header){ + returnValue += (returnValue ? ' ' : '') + header; + } + + // Quotes + if(state.quote){ + returnValue += (returnValue ? ' ' : '') + quote; + } + + // Check valud and return + if(!returnValue){ + returnValue = null; + } + return returnValue; + + } + + function handleText(stream, state) { + if (stream.match(textRE, true)) { + return getType(state); + } + return undefined; + } + + function inlineNormal(stream, state) { + var style = state.text(stream, state) + if (typeof style !== 'undefined') + return style; + + var ch = stream.next(); + + if (ch === '\\') { + stream.next(); + return getType(state); + } + if (ch === '`') { + return switchInline(stream, state, inlineElement(code, '`')); + } + if (ch === '[') { + return switchInline(stream, state, linkText); + } + if (ch === '<' && stream.match(/^\w/, false)) { + stream.backUp(1); + return switchBlock(stream, state, htmlBlock); + } + + var t = getType(state); + if (ch === '*' || ch === '_') { + if (stream.eat(ch)) { + return (state.strong = !state.strong) ? getType(state) : t; + } + return (state.em = !state.em) ? getType(state) : t; + } + + return getType(state); + } + + function linkText(stream, state) { + while (!stream.eol()) { + var ch = stream.next(); + if (ch === '\\') stream.next(); + if (ch === ']') { + state.inline = state.f = linkHref; + return linktext; + } + } + return linktext; + } + + function linkHref(stream, state) { + stream.eatSpace(); + var ch = stream.next(); + if (ch === '(' || ch === '[') { + return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']')); + } + return 'error'; + } + + function footnoteLink(stream, state) { + if (stream.match(/^[^\]]*\]:/, true)) { + state.f = footnoteUrl; + return linktext; + } + return switchInline(stream, state, inlineNormal); + } + + function footnoteUrl(stream, state) { + stream.eatSpace(); + stream.match(/^[^\s]+/, true); + state.f = state.inline = inlineNormal; + return linkhref; + } + + function inlineRE(endChar) { + if (!inlineRE[endChar]) { + // match any not-escaped-non-endChar and any escaped char + // then match endChar or eol + inlineRE[endChar] = new RegExp('^(?:[^\\\\\\' + endChar + ']|\\\\.)*(?:\\' + endChar + '|$)'); + } + return inlineRE[endChar]; + } + + function inlineElement(type, endChar, next) { + next = next || inlineNormal; + return function(stream, state) { + stream.match(inlineRE(endChar)); + state.inline = state.f = next; + return type; + }; + } + + return { + startState: function() { + return { + f: blockNormal, + + block: blockNormal, + htmlState: htmlMode.startState(), + indentation: 0, + + inline: inlineNormal, + text: handleText, + em: false, + strong: false, + header: false, + quote: false + }; + }, + + copyState: function(s) { + return { + f: s.f, + + block: s.block, + htmlState: CodeMirror.copyState(htmlMode, s.htmlState), + indentation: s.indentation, + + inline: s.inline, + text: s.text, + em: s.em, + strong: s.strong, + header: s.header, + quote: s.quote + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + // Reset EM state + state.em = false; + // Reset STRONG state + state.strong = false; + // Reset state.header + state.header = false; + // Reset state.quote + state.quote = false; + + state.f = state.block; + var previousIndentation = state.indentation + , currentIndentation = 0; + while (previousIndentation > 0) { + if (stream.eat(' ')) { + previousIndentation--; + currentIndentation++; + } else if (previousIndentation >= 4 && stream.eat('\t')) { + previousIndentation -= 4; + currentIndentation += 4; + } else { + break; + } + } + state.indentation = currentIndentation; + + if (currentIndentation > 0) return null; + } + return state.f(stream, state); + }, + + getType: getType + }; + +}); + +CodeMirror.defineMIME("text/x-markdown", "markdown"); diff --git a/static/js/CodeMirror/xml.js b/static/js/CodeMirror/xml.js new file mode 100644 index 0000000000..f467bddccc --- /dev/null +++ b/static/js/CodeMirror/xml.js @@ -0,0 +1,267 @@ +CodeMirror.defineMode("xml", function(config, parserConfig) { + var indentUnit = config.indentUnit; + var Kludges = parserConfig.htmlMode ? { + autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true, + "meta": true, "col": true, "frame": true, "base": true, "area": true}, + doNotIndent: {"pre": true}, + allowUnquoted: true, + allowMissing: false + } : {autoSelfClosers: {}, doNotIndent: {}, allowUnquoted: false, allowMissing: false}; + var alignCDATA = parserConfig.alignCDATA; + + // Return variables for tokenizers + var tagName, type; + + function inText(stream, state) { + function chain(parser) { + state.tokenize = parser; + return parser(stream, state); + } + + var ch = stream.next(); + if (ch == "<") { + if (stream.eat("!")) { + if (stream.eat("[")) { + if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); + else return null; + } + else if (stream.match("--")) return chain(inBlock("comment", "-->")); + else if (stream.match("DOCTYPE", true, true)) { + stream.eatWhile(/[\w\._\-]/); + return chain(doctype(1)); + } + else return null; + } + else if (stream.eat("?")) { + stream.eatWhile(/[\w\._\-]/); + state.tokenize = inBlock("meta", "?>"); + return "meta"; + } + else { + type = stream.eat("/") ? "closeTag" : "openTag"; + stream.eatSpace(); + tagName = ""; + var c; + while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; + state.tokenize = inTag; + return "tag"; + } + } + else if (ch == "&") { + var ok; + if (stream.eat("#")) { + if (stream.eat("x")) { + ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); + } else { + ok = stream.eatWhile(/[\d]/) && stream.eat(";"); + } + } else { + ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); + } + return ok ? "atom" : "error"; + } + else { + stream.eatWhile(/[^&<]/); + return null; + } + } + + function inTag(stream, state) { + var ch = stream.next(); + if (ch == ">" || (ch == "/" && stream.eat(">"))) { + state.tokenize = inText; + type = ch == ">" ? "endTag" : "selfcloseTag"; + return "tag"; + } + else if (ch == "=") { + type = "equals"; + return null; + } + else if (/[\'\"]/.test(ch)) { + state.tokenize = inAttribute(ch); + return state.tokenize(stream, state); + } + else { + stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/); + return "word"; + } + } + + function inAttribute(quote) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.next() == quote) { + state.tokenize = inTag; + break; + } + } + return "string"; + }; + } + + function inBlock(style, terminator) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + state.tokenize = inText; + break; + } + stream.next(); + } + return style; + }; + } + function doctype(depth) { + return function(stream, state) { + var ch; + while ((ch = stream.next()) != null) { + if (ch == "<") { + state.tokenize = doctype(depth + 1); + return state.tokenize(stream, state); + } else if (ch == ">") { + if (depth == 1) { + state.tokenize = inText; + break; + } else { + state.tokenize = doctype(depth - 1); + return state.tokenize(stream, state); + } + } + } + return "meta"; + }; + } + + var curState, setStyle; + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); + } + function cont() { + pass.apply(null, arguments); + return true; + } + + function pushContext(tagName, startOfLine) { + var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent); + curState.context = { + prev: curState.context, + tagName: tagName, + indent: curState.indented, + startOfLine: startOfLine, + noIndent: noIndent + }; + } + function popContext() { + if (curState.context) curState.context = curState.context.prev; + } + + function element(type) { + if (type == "openTag") { + curState.tagName = tagName; + return cont(attributes, endtag(curState.startOfLine)); + } else if (type == "closeTag") { + var err = false; + if (curState.context) { + err = curState.context.tagName != tagName; + } else { + err = true; + } + if (err) setStyle = "error"; + return cont(endclosetag(err)); + } + return cont(); + } + function endtag(startOfLine) { + return function(type) { + if (type == "selfcloseTag" || + (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) + return cont(); + if (type == "endTag") {pushContext(curState.tagName, startOfLine); return cont();} + return cont(); + }; + } + function endclosetag(err) { + return function(type) { + if (err) setStyle = "error"; + if (type == "endTag") { popContext(); return cont(); } + setStyle = "error"; + return cont(arguments.callee); + } + } + + function attributes(type) { + if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);} + if (type == "endTag" || type == "selfcloseTag") return pass(); + setStyle = "error"; + return cont(attributes); + } + function attribute(type) { + if (type == "equals") return cont(attvalue, attributes); + if (!Kludges.allowMissing) setStyle = "error"; + return (type == "endTag" || type == "selfcloseTag") ? pass() : cont(); + } + function attvalue(type) { + if (type == "string") return cont(attvaluemaybe); + if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();} + setStyle = "error"; + return (type == "endTag" || type == "selfCloseTag") ? pass() : cont(); + } + function attvaluemaybe(type) { + if (type == "string") return cont(attvaluemaybe); + else return pass(); + } + + return { + startState: function() { + return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null}; + }, + + token: function(stream, state) { + if (stream.sol()) { + state.startOfLine = true; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + + setStyle = type = tagName = null; + var style = state.tokenize(stream, state); + state.type = type; + if ((style || type) && style != "comment") { + curState = state; + while (true) { + var comb = state.cc.pop() || element; + if (comb(type || style)) break; + } + } + state.startOfLine = false; + return setStyle || style; + }, + + indent: function(state, textAfter, fullLine) { + var context = state.context; + if ((state.tokenize != inTag && state.tokenize != inText) || + context && context.noIndent) + return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; + if (alignCDATA && / --> - + <%! from django.core.urlresolvers import reverse diff --git a/templates/simplewiki_edit.html b/templates/simplewiki_edit.html index 435870071d..a8c36e247c 100644 --- a/templates/simplewiki_edit.html +++ b/templates/simplewiki_edit.html @@ -9,22 +9,54 @@ <%block name="wiki_head"> + + + + + + + + + + + + From a858c91ffca3902b1699e4d95e1301aa803b0f50 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Mon, 26 Mar 2012 11:03:14 -0700 Subject: [PATCH 03/36] Simple css change to get a decent looking demo. --- templates/simplewiki_edit.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/templates/simplewiki_edit.html b/templates/simplewiki_edit.html index a8c36e247c..8b40689aef 100644 --- a/templates/simplewiki_edit.html +++ b/templates/simplewiki_edit.html @@ -19,13 +19,14 @@ From 5580c62ee4696d140dcc973bb4717c22f42222f8 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Mon, 26 Mar 2012 13:48:11 -0700 Subject: [PATCH 04/36] Wiki editor now has wrapped lines. --- static/js/CodeMirror/codemirror.js | 88 +++++++++++---------------- static/js/CodeMirror/mitx_markdown.js | 3 + templates/simplewiki_edit.html | 3 +- 3 files changed, 39 insertions(+), 55 deletions(-) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index c7d3c86899..b6d2d21bdd 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -693,31 +693,6 @@ var CodeMirror = (function() { if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); doc.insert(from.line + 1, added); } - if (options.lineWrapping) { - var perLine = scroller.clientWidth / charWidth() - 3; - doc.iter(from.line, from.line + newText.length, function(line) { - if (line.hidden) return; - var guess = Math.ceil(line.text.length / perLine) || 1; - if (guess != line.height) updateLineHeight(line, guess); - }); - } else { - doc.iter(from.line, i + newText.length, function(line) { - var l = line.text; - if (l.length > maxLineLength) { - maxLine = l; maxLineLength = l.length; maxWidth = null; - recomputeMaxLength = false; - } - }); - if (recomputeMaxLength) { - maxLineLength = 0; maxLine = ""; maxWidth = null; - doc.iter(0, doc.size, function(line) { - var l = line.text; - if (l.length > maxLineLength) { - maxLineLength = l.length; maxLine = l; - } - }); - } - } // Add these lines to the work array, so that they will be // highlighted. Adjust work lines if lines were added/removed. @@ -740,6 +715,36 @@ var CodeMirror = (function() { cur.next = changeObj; } else textChanged = changeObj; + + if (options.lineWrapping) { + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(from.line, from.line + newText.length, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (line.isWidgetBlock) + guess = line.styles[1].size(line.text).height / textHeight(); + if (guess != line.height) updateLineHeight(line, guess); + }); + } else { + //TODO: update height here for widget blocks + doc.iter(from.line, i + newText.length, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLine = l; maxLineLength = l.length; maxWidth = null; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) { + maxLineLength = 0; maxLine = ""; maxWidth = null; + doc.iter(0, doc.size, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLineLength = l.length; maxLine = l; + } + }); + } + } + // Update the selection function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); @@ -938,7 +943,7 @@ var CodeMirror = (function() { intact.sort(function(a, b) {return a.domStart - b.domStart;}); var th = textHeight(), gutterDisplay = gutter.style.display; - //lineDiv.style.display = "none"; + lineDiv.style.display = "none"; patchDisplay(from, to, intact); lineDiv.style.display = gutter.style.display = ""; @@ -964,8 +969,9 @@ var CodeMirror = (function() { maxWidth = scroller.clientWidth; var curNode = lineDiv.firstChild, heightChanged = false; doc.iter(showingFrom, showingTo, function(line) { - if (!line.hidden) { + if (!line.hidden && !line.isWidgetBlock) { //TODO: We should handle widget blocks better here var height = Math.round(curNode.offsetHeight / th) || 1; + if (line.isWidgetBlock) height = line.styles[1].size(line.text).height / textHeight(); if (line.height != height) { updateLineHeight(line, height); gutterDirty = heightChanged = true; @@ -1055,7 +1061,6 @@ var CodeMirror = (function() { var insertChild = scratch.firstChild; lineDiv.insertBefore(insertChild, curNode); line.nodeAdded(insertChild); - line.setHeight( insertChild.clientHeight / text_height ); } else { curNode = curNode.nextSibling; } @@ -1354,7 +1359,7 @@ var CodeMirror = (function() { wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); maxWidth = null; maxLine = ""; doc.iter(0, doc.size, function(line) { - if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); + if (line.height != 1 && !line.hidden && !line.isWidgetBlock) updateLineHeight(line, 1); if (line.text.length > maxLine.length) maxLine = line.text; }); } @@ -2403,17 +2408,6 @@ var CodeMirror = (function() { if (this.isWidgetBlock) this.styles[1].callback(node, this); //this.setHeight(node.clientHeight); }, - setHeight: function(newHeight) { - if (this.isWidgetBlock) newHeight = 300 / 14.0; - this.growHeight(newHeight - this.height); - }, - //This is a bottom-up height change. InsertHeight is a top-down height change - growHeight: function(deltaHeight) { - if (this.parent && deltaHeight != 0) { - this.parent.growHeight(deltaHeight); - } - this.height = this.height + deltaHeight; - }, // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). getTokenAt: function(mode, state, ch) { @@ -2562,13 +2556,6 @@ var CodeMirror = (function() { collapse: function(lines) { lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); }, - //This is a bottom-up height change. InsertHeight is a top-down height change - growHeight: function(deltaHeight) { - if (this.parent && deltaHeight != 0) { - this.parent.growHeight(deltaHeight) - } - this.height = this.height + deltaHeight; - }, insertHeight: function(at, lines, height) { this.height += height; this.lines.splice.apply(this.lines, [at, 0].concat(lines)); @@ -2643,13 +2630,6 @@ var CodeMirror = (function() { at -= sz; } }, - //This is a bottom-up height change. InsertHeight is a top-down height change - growHeight: function(deltaHeight) { - if (this.parent && deltaHeight != 0) { - this.parent.growHeight(deltaHeight) - } - this.height = this.height + deltaHeight; - }, maybeSpill: function() { if (this.children.length <= 10) return; var me = this; diff --git a/static/js/CodeMirror/mitx_markdown.js b/static/js/CodeMirror/mitx_markdown.js index 02d6e609c2..9a29b65fe0 100644 --- a/static/js/CodeMirror/mitx_markdown.js +++ b/static/js/CodeMirror/mitx_markdown.js @@ -70,6 +70,9 @@ CodeMirror.defineMode("mitx_markdown", function(cmCfg, modeCfg) { return html; }, + size: function(text) { + return {width: 400, height:302}; + }, callback: function(node, line) { update_schematics(); var schmInput = node.firstChild; diff --git a/templates/simplewiki_edit.html b/templates/simplewiki_edit.html index 8b40689aef..82c0456473 100644 --- a/templates/simplewiki_edit.html +++ b/templates/simplewiki_edit.html @@ -38,7 +38,8 @@ var editor = CodeMirror.fromTextArea(document.getElementById("id_contents"), { mode: 'mitx_markdown', matchBrackets: true, - theme: "default" + theme: "default", + lineWrapping: true, }); //Store the inital contents so we can compare for unsaved changes From 952b75496630597d688ec8f43ecf8ffbea49f8f0 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Mon, 26 Mar 2012 17:15:45 -0700 Subject: [PATCH 05/36] New method works without line wrapping too --- static/js/CodeMirror/codemirror.js | 10 +++++++--- templates/simplewiki_edit.html | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index b6d2d21bdd..bc5927b831 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -697,8 +697,8 @@ var CodeMirror = (function() { // Add these lines to the work array, so that they will be // highlighted. Adjust work lines if lines were added/removed. var newWork = [], lendiff = newText.length - nlines - 1; - for (var i = 0, l = work.length; i < l; ++i) { - var task = work[i]; + for (var j = 0, l = work.length; j < l; ++j) { + var task = work[j]; if (task < from.line) newWork.push(task); else if (task > to.line) newWork.push(task + lendiff); } @@ -727,12 +727,16 @@ var CodeMirror = (function() { }); } else { //TODO: update height here for widget blocks - doc.iter(from.line, i + newText.length, function(line) { + doc.iter(from.line, from.line + newText.length, function(line) { var l = line.text; if (l.length > maxLineLength) { maxLine = l; maxLineLength = l.length; maxWidth = null; recomputeMaxLength = false; } + if (line.isWidgetBlock) { + var guess = line.styles[1].size(line.text).height / textHeight(); + if (guess != line.height) updateLineHeight(line, guess); + } }); if (recomputeMaxLength) { maxLineLength = 0; maxLine = ""; maxWidth = null; diff --git a/templates/simplewiki_edit.html b/templates/simplewiki_edit.html index 82c0456473..1c5ebd40fc 100644 --- a/templates/simplewiki_edit.html +++ b/templates/simplewiki_edit.html @@ -39,7 +39,7 @@ mode: 'mitx_markdown', matchBrackets: true, theme: "default", - lineWrapping: true, + lineWrapping: false, }); //Store the inital contents so we can compare for unsaved changes From 43318b72134e40a8ed5348d82c624b0ae7fc554e Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Tue, 27 Mar 2012 14:33:24 -0700 Subject: [PATCH 06/36] Fixing deletion / insertion bugs and inconsistencies --- static/js/CodeMirror/codemirror.js | 51 ++++++++++++++++++--------- static/js/CodeMirror/mitx_markdown.js | 8 ++--- templates/simplewiki_edit.html | 10 +++--- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index bc5927b831..a34d520891 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -616,6 +616,24 @@ var CodeMirror = (function() { // Afterwards, set the selection to selFrom, selTo. function updateLines(from, to, newText, selFrom, selTo) { if (suppressEdits) return; + + if (from.ch > 0 && newText[0] != '' && getLine(from.line).isWidgetBlock) { + newText.unshift(''); + var widgetLine = getLine(from.line); + function moveSel(sel) { + if (sel.line == from.line && sel.ch > 0){ + return {line: sel.line + 1, ch: sel.ch - widgetLine.text.length}; + } else if (sel.line > from.line) { + return {line: sel.line + 1, ch: sel.ch}; + } + } + selFrom = moveSel(selFrom, widgetLine); + selTo = moveSel(selTo, widgetLine); + } + if (to.ch == 0 && newText[newText.length - 1] != '' && getLine(to.line).isWidgetBlock) { + newText.push(''); + } + if (history) { var old = []; doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); }); @@ -726,7 +744,6 @@ var CodeMirror = (function() { if (guess != line.height) updateLineHeight(line, guess); }); } else { - //TODO: update height here for widget blocks doc.iter(from.line, from.line + newText.length, function(line) { var l = line.text; if (l.length > maxLineLength) { @@ -779,23 +796,11 @@ var CodeMirror = (function() { return end; } function replaceSelection(code, collapse) { - var reposition = false; - if (code.length > 0) { - var fromLine = getLine(sel.from.line), toLine = getLine(sel.to.line); - if (fromLine.isWidgetBlock && sel.from.ch == fromLine.text.length) { - code = "\n" + code; - } else if (toLine.isWidgetBlock && sel.to.ch == 0) { - code = code + "\n"; - reposition = true; - } - } - replaceRange1(splitLines(code), sel.from, sel.to, function(end) { if (collapse == "end") return {from: end, to: end}; else if (collapse == "start") return {from: sel.from, to: sel.from}; else return {from: sel.from, to: end}; }); - if (reposition) moveH(-1, "char"); } function replaceRange1(code, from, to, computeSel) { var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length; @@ -1266,9 +1271,19 @@ var CodeMirror = (function() { setCursor(pos.line, pos.ch, true); } function deleteH(dir, unit) { - if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); - else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); - else replaceRange("", sel.from, findPosH(dir, unit)); + var from = sel.from; + var to = sel.to; + if (posEq(sel.from, sel.to)) { + if (dir < 0) { + from = findPosH(dir, unit); + if (getLine(from.line).isWidgetBlock) from.ch = 0; + } + else { + to = findPosH(dir, unit); + if (getLine(to.line).isWidgetBlock) to.ch = getLine(to.line).text.length; + } + } + replaceRange("", from, to); userSelChange = true; } var goalColumn = null; @@ -1554,6 +1569,10 @@ var CodeMirror = (function() { var tempId = Math.floor(Math.random() * 0xffffff).toString(16); function measureLine(line, ch) { if (ch == 0) return {top: 0, left: 0}; + if (line.isWidgetBlock) { + var size = line.styles[1].size(line.text); + return {top: -1, left: size.width}; + } var extra = ""; // Include extra text at the end to make sure the measured line is wrapped in the right way. if (options.lineWrapping) { diff --git a/static/js/CodeMirror/mitx_markdown.js b/static/js/CodeMirror/mitx_markdown.js index 9a29b65fe0..aa63967c58 100644 --- a/static/js/CodeMirror/mitx_markdown.js +++ b/static/js/CodeMirror/mitx_markdown.js @@ -2,7 +2,7 @@ $(function(){ $(document).ready(function() { $("a[rel*=leanModal]").leanModal(); - $("body").append('
'); + $("body").append('
'); //This is the editor that pops up as a modal var editorCircuit = $("#schematic_editor").get(0); @@ -65,13 +65,13 @@ CodeMirror.defineMode("mitx_markdown", function(cmCfg, modeCfg) { //TODO: We need real html escaping here circuit_value = CodeMirror.htmlEscape(circuit_value);// circuit_value.replace("\"", "'"); - var html = ""; + var html = "" + + ""; return html; }, size: function(text) { - return {width: 400, height:302}; + return {width: 150, height:154}; }, callback: function(node, line) { update_schematics(); diff --git a/templates/simplewiki_edit.html b/templates/simplewiki_edit.html index 1c5ebd40fc..75e1fec325 100644 --- a/templates/simplewiki_edit.html +++ b/templates/simplewiki_edit.html @@ -45,11 +45,11 @@ //Store the inital contents so we can compare for unsaved changes var initial_contents = editor.getValue(); - window.onbeforeunload = function askConfirm() { //Warn the user before they navigate away - if ( editor.getValue() != initial_contents ) { - return "You have made changes to the article that have not been saved yet."; - } - }; + // window.onbeforeunload = function askConfirm() { //Warn the user before they navigate away + // if ( editor.getValue() != initial_contents ) { + // return "You have made changes to the article that have not been saved yet."; + // } + // }; $("#submit_edit").click(function() { initial_contents = editor.getValue(); From 32384c8eb9f3694c0e8cc261c4b0973780f33431 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 28 Mar 2012 00:36:06 -0700 Subject: [PATCH 07/36] Fixed a lot of bugs with selection in codemirror. Seems to work pretty solidly now\! --- static/js/CodeMirror/codemirror.js | 62 +++++++++++++++------------ static/js/CodeMirror/mitx_markdown.js | 2 +- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index a34d520891..261070d6af 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -498,17 +498,17 @@ var CodeMirror = (function() { if (!bound) return false; } var prevShift = shiftSelecting; - try { + // try { //TODO: Reenable this try/catch if (options.readOnly) suppressEdits = true; if (dropShift) shiftSelecting = null; bound(instance); - } catch(e) { - if (e != Pass) throw e; - return false; - } finally { - shiftSelecting = prevShift; - suppressEdits = false; - } + // } catch(e) { + // if (e != Pass) throw e; + // return false; + // } finally { + // shiftSelecting = prevShift; + // suppressEdits = false; + // } return true; } function handleKeyBinding(e) { @@ -617,7 +617,7 @@ var CodeMirror = (function() { function updateLines(from, to, newText, selFrom, selTo) { if (suppressEdits) return; - if (from.ch > 0 && newText[0] != '' && getLine(from.line).isWidgetBlock) { + if (from.ch > 0 && (newText[0] != '' || newText.length == 1) && getLine(from.line).widgetFunction) { newText.unshift(''); var widgetLine = getLine(from.line); function moveSel(sel) { @@ -630,7 +630,7 @@ var CodeMirror = (function() { selFrom = moveSel(selFrom, widgetLine); selTo = moveSel(selTo, widgetLine); } - if (to.ch == 0 && newText[newText.length - 1] != '' && getLine(to.line).isWidgetBlock) { + if (to.ch == 0 && (newText[newText.length - 1] != '' || newText.length == 1) && getLine(to.line).widgetFunction) { newText.push(''); } @@ -739,8 +739,8 @@ var CodeMirror = (function() { doc.iter(from.line, from.line + newText.length, function(line) { if (line.hidden) return; var guess = Math.ceil(line.text.length / perLine) || 1; - if (line.isWidgetBlock) - guess = line.styles[1].size(line.text).height / textHeight(); + if (line.widgetFunction) + guess = line.widgetFunction.size(line.text).height / textHeight(); if (guess != line.height) updateLineHeight(line, guess); }); } else { @@ -750,8 +750,8 @@ var CodeMirror = (function() { maxLine = l; maxLineLength = l.length; maxWidth = null; recomputeMaxLength = false; } - if (line.isWidgetBlock) { - var guess = line.styles[1].size(line.text).height / textHeight(); + if (line.widgetFunction) { + var guess = line.widgetFunction.size(line.text).height / textHeight(); if (guess != line.height) updateLineHeight(line, guess); } }); @@ -978,9 +978,9 @@ var CodeMirror = (function() { maxWidth = scroller.clientWidth; var curNode = lineDiv.firstChild, heightChanged = false; doc.iter(showingFrom, showingTo, function(line) { - if (!line.hidden && !line.isWidgetBlock) { //TODO: We should handle widget blocks better here + if (!line.hidden && !line.widgetFunction) { //TODO: We should handle widget blocks better here var height = Math.round(curNode.offsetHeight / th) || 1; - if (line.isWidgetBlock) height = line.styles[1].size(line.text).height / textHeight(); + if (line.widgetFunction) height = line.widgetFunction.size(line.text).height / textHeight(); if (line.height != height) { updateLineHeight(line, height); gutterDirty = heightChanged = true; @@ -1058,7 +1058,7 @@ var CodeMirror = (function() { if (line.hidden) var html = scratch.innerHTML = "
";
           else {
             var html = line.getHTML(makeTab);
-            if (!line.isWidgetBlock) {
+            if (!line.widgetFunction) {
               html = '' + html + '
'; } // Kludge to make sure the styled element lies behind the selection (by z-index) @@ -1228,7 +1228,8 @@ var CodeMirror = (function() { function clipPos(pos) { if (pos.line < 0) return {line: 0, ch: 0}; if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length}; - var ch = pos.ch, linelen = getLine(pos.line).text.length; + var ch = pos.ch, line = getLine(pos.line), linelen =line.text.length; + if (line.widgetFunction && ch != 0) return {line: pos.line, ch: linelen}; if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; else if (ch < 0) return {line: pos.line, ch: 0}; else return pos; @@ -1247,7 +1248,7 @@ var CodeMirror = (function() { if (ch == (dir < 0 ? 0 : lineObj.text.length)) { if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; else return false; - } else if (lineObj.isWidgetBlock) { //Select the entire line + } else if (lineObj.widgetFunction) { //Select the entire line ch = dir < 0 ? 0 : lineObj.text.length; } else ch += dir; return true; @@ -1276,11 +1277,11 @@ var CodeMirror = (function() { if (posEq(sel.from, sel.to)) { if (dir < 0) { from = findPosH(dir, unit); - if (getLine(from.line).isWidgetBlock) from.ch = 0; + if (getLine(from.line).widgetFunction) from.ch = 0; } else { to = findPosH(dir, unit); - if (getLine(to.line).isWidgetBlock) to.ch = getLine(to.line).text.length; + if (getLine(to.line).widgetFunction) to.ch = getLine(to.line).text.length; } } replaceRange("", from, to); @@ -1378,7 +1379,7 @@ var CodeMirror = (function() { wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); maxWidth = null; maxLine = ""; doc.iter(0, doc.size, function(line) { - if (line.height != 1 && !line.hidden && !line.isWidgetBlock) updateLineHeight(line, 1); + if (line.height != 1 && !line.hidden && !line.widgetFunction) updateLineHeight(line, 1); if (line.text.length > maxLine.length) maxLine = line.text; }); } @@ -1569,8 +1570,8 @@ var CodeMirror = (function() { var tempId = Math.floor(Math.random() * 0xffffff).toString(16); function measureLine(line, ch) { if (ch == 0) return {top: 0, left: 0}; - if (line.isWidgetBlock) { - var size = line.styles[1].size(line.text); + if (line.widgetFunction) { + var size = line.widgetFunction.size(line.text); return {top: -1, left: size.width}; } var extra = ""; @@ -2288,7 +2289,7 @@ var CodeMirror = (function() { this.height = 1; this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null; this.stateAfter = this.parent = this.hidden = null; - this.isWidgetBlock = false; + this.widgetFunction = null; } Line.inheritMarks = function(text, orig) { var ln = new Line(text), mk = orig && orig.marked; @@ -2421,14 +2422,19 @@ var CodeMirror = (function() { } if (st.length != pos) {st.length = pos; changed = true;} if (pos && st[pos-2] != prevWord) changed = true; - this.isWidgetBlock = (st.length == 2 && st[1] && typeof st[1] == 'object'); + if (st.length == 2 && typeof st[1] == 'object') { + this.widgetFunction = st[1]; + st[1] = null; + } else { + this.widgetFunction = null; + } // Short lines with simple highlights return null, and are // counted as changed by the driver because they are likely to // highlight the same way in various contexts. return changed || (st.length < 5 && this.text.length < 10 ? null : false); }, nodeAdded: function(node) { - if (this.isWidgetBlock) this.styles[1].callback(node, this); + if (this.widgetFunction) this.widgetFunction.callback(node, this); //this.setHeight(node.clientHeight); }, // Fetch the parser token for a given character. Useful for hacks @@ -2480,7 +2486,7 @@ var CodeMirror = (function() { } var st = this.styles, allText = this.text, marked = this.marked; var len = allText.length; - if (this.isWidgetBlock) return st[1].creator(allText); + if (this.widgetFunction) return this.widgetFunction.creator(allText); if (endAt != null) len = Math.min(endAt, len); function styleToClass(style) { if (!style) return null; diff --git a/static/js/CodeMirror/mitx_markdown.js b/static/js/CodeMirror/mitx_markdown.js index aa63967c58..9b95d96d19 100644 --- a/static/js/CodeMirror/mitx_markdown.js +++ b/static/js/CodeMirror/mitx_markdown.js @@ -35,7 +35,7 @@ $(function(){ editingCircuit.schematic.load_schematic(saving_circuit, ""); if (editingCircuit.codeMirrorLine) { - editingCircuit.codeMirrorLine.text = "circuit-schematic:" + saving_circuit; + editingCircuit.codeMirrorLine.replace(0, null, "circuit-schematic:" + saving_circuit); } $(".modal_close").first().click(); From 3c015bb0a9716ac6a3da7c9d98ae54e374ada96a Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 28 Mar 2012 00:56:44 -0700 Subject: [PATCH 08/36] Another codemirror fix. --- static/js/CodeMirror/codemirror.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index 261070d6af..f517176c46 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -753,6 +753,8 @@ var CodeMirror = (function() { if (line.widgetFunction) { var guess = line.widgetFunction.size(line.text).height / textHeight(); if (guess != line.height) updateLineHeight(line, guess); + } else if (line.height != 1) { + updateLineHeight(line, 1); } }); if (recomputeMaxLength) { From 99b8a5110163de995d09cc0b8ae900f574f15824 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 28 Mar 2012 01:00:33 -0700 Subject: [PATCH 09/36] Removed debug comments. --- static/js/CodeMirror/codemirror.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index f517176c46..ed92bc8378 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -498,17 +498,17 @@ var CodeMirror = (function() { if (!bound) return false; } var prevShift = shiftSelecting; - // try { //TODO: Reenable this try/catch + try { if (options.readOnly) suppressEdits = true; if (dropShift) shiftSelecting = null; bound(instance); - // } catch(e) { - // if (e != Pass) throw e; - // return false; - // } finally { - // shiftSelecting = prevShift; - // suppressEdits = false; - // } + } catch(e) { + if (e != Pass) throw e; + return false; + } finally { + shiftSelecting = prevShift; + suppressEdits = false; + } return true; } function handleKeyBinding(e) { From f95aa496facf7bb4266621f38bd6e5a934edb108 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 28 Mar 2012 14:09:21 -0700 Subject: [PATCH 10/36] Better compatibility with the Codemirror editor and the markdown circuit extension. --- djangoapps/simplewiki/mdx_circuit.py | 40 ++++++++++++++++++++------- djangoapps/simplewiki/models.py | 1 - static/js/CodeMirror/codemirror.js | 2 +- static/js/CodeMirror/mitx_markdown.js | 2 ++ templates/simplewiki_edit.html | 2 +- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/djangoapps/simplewiki/mdx_circuit.py b/djangoapps/simplewiki/mdx_circuit.py index 33377eb019..72e432012f 100755 --- a/djangoapps/simplewiki/mdx_circuit.py +++ b/djangoapps/simplewiki/mdx_circuit.py @@ -5,12 +5,14 @@ Image Circuit Extension for Python-Markdown circuit:name becomes the circuit. ''' +import markdown +import re import simplewiki.settings as settings from mitxmako.shortcuts import render_to_response, render_to_string -import markdown + try: # Markdown 2.1.0 changed from 2.0.3. We try importing the new version first, # but import the 2.0.3 version if it fails @@ -22,22 +24,40 @@ class CircuitExtension(markdown.Extension): def __init__(self, configs): for key, value in configs : self.setConfig(key, value) - - def add_inline(self, md, name, klass, re): - pattern = klass(re) - pattern.md = md - pattern.ext = self - md.inlinePatterns.add(name, pattern, ".*)$') + ## Because Markdown treats contigous lines as one block of text, it is hard to match + ## a regex that must occupy the whole line (like the circuit regex). This is why we have + ## a preprocessor that inspects the lines and replaces the matched lines with text that is + ## easier to match + md.preprocessors.add('circuit', CircuitPreprocessor(md), "_begin") + + pattern = CircuitLink(r'processed-schematic:(?P.*?)processed-schematic-end') + pattern.md = md + pattern.ext = self + md.inlinePatterns.add('circuit', pattern, ".*)$') + + def run(self, lines): + new_lines = [] + for line in lines: + m = self.preRegex.match(line) + if m: + new_lines.append('processed-schematic:{0}processed-schematic-end'.format( m.group('data') )) + else: + new_lines.append(line) + return new_lines + class CircuitLink(markdown.inlinepatterns.Pattern): def handleMatch(self, m): data = m.group('data') - ##TODO: We need to html escape the data - return etree.fromstring("") + return etree.fromstring("") def makeExtension(configs=None) : diff --git a/djangoapps/simplewiki/models.py b/djangoapps/simplewiki/models.py index 33c9b0403e..71a57c601c 100644 --- a/djangoapps/simplewiki/models.py +++ b/djangoapps/simplewiki/models.py @@ -276,7 +276,6 @@ class Revision(models.Model): # Create pre-parsed contents - no need to parse on-the-fly ext = WIKI_MARKDOWN_EXTENSIONS ext += ["wikipath(base_url=%s)" % reverse('wiki_view', args=('/',))] - print ext self.contents_parsed = markdown(self.contents, extensions=ext, safe_mode='escape',) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index ed92bc8378..c840de05b7 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -980,7 +980,7 @@ var CodeMirror = (function() { maxWidth = scroller.clientWidth; var curNode = lineDiv.firstChild, heightChanged = false; doc.iter(showingFrom, showingTo, function(line) { - if (!line.hidden && !line.widgetFunction) { //TODO: We should handle widget blocks better here + if (!line.hidden && !line.widgetFunction) { var height = Math.round(curNode.offsetHeight / th) || 1; if (line.widgetFunction) height = line.widgetFunction.size(line.text).height / textHeight(); if (line.height != height) { diff --git a/static/js/CodeMirror/mitx_markdown.js b/static/js/CodeMirror/mitx_markdown.js index 9b95d96d19..9991a4f05c 100644 --- a/static/js/CodeMirror/mitx_markdown.js +++ b/static/js/CodeMirror/mitx_markdown.js @@ -77,6 +77,8 @@ CodeMirror.defineMode("mitx_markdown", function(cmCfg, modeCfg) { update_schematics(); var schmInput = node.firstChild; schmInput.codeMirrorLine = line; + schmInput.schematic.always_draw_grid = true; + schmInput.schematic.redraw_background(); $(node).leanModal(); } }; diff --git a/templates/simplewiki_edit.html b/templates/simplewiki_edit.html index 75e1fec325..85059a1766 100644 --- a/templates/simplewiki_edit.html +++ b/templates/simplewiki_edit.html @@ -39,7 +39,7 @@ mode: 'mitx_markdown', matchBrackets: true, theme: "default", - lineWrapping: false, + lineWrapping: true, }); //Store the inital contents so we can compare for unsaved changes From bb15d923813345fc487ccea29e6a633974f02768 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 28 Mar 2012 14:20:08 -0700 Subject: [PATCH 11/36] Fixed leanModal bug that caused lots of overlays to be added. --- static/js/jquery.leanModal.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/static/js/jquery.leanModal.js b/static/js/jquery.leanModal.js index 3d2dd16bb9..dce1b51804 100644 --- a/static/js/jquery.leanModal.js +++ b/static/js/jquery.leanModal.js @@ -10,9 +10,12 @@ closeButton:'.modal_close' } - var overlay = $("
"); + var overlay = $("#lean_overlay"); + if (overlay.length == 0) { + overlay = $("
"); + $("body").append(overlay); + } - $("body").append(overlay); options = $.extend(defaults, options); From f62a43965b1875d48da7fb48dde70dc62fa2530a Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 28 Mar 2012 22:50:23 -0700 Subject: [PATCH 12/36] Fixed bug for selecting lines with non-integer heights. It put up a fight... --- static/js/CodeMirror/codemirror.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index c840de05b7..b20a7d033b 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -1613,7 +1613,7 @@ var CodeMirror = (function() { var lineNo = lineAtHeight(doc, heightPos); if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; var lineObj = getLine(lineNo), text = lineObj.text; - var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; + var tw = options.lineWrapping, innerOff = tw ? Math.floor(heightPos - heightAtLine(doc, lineNo)) : 0; if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; function getX(len) { var sp = measureLine(lineObj, len); @@ -2437,7 +2437,6 @@ var CodeMirror = (function() { }, nodeAdded: function(node) { if (this.widgetFunction) this.widgetFunction.callback(node, this); - //this.setHeight(node.clientHeight); }, // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). From b2fbf537e4f90d06b9aa7932dd683cad9ac570b2 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 29 Mar 2012 00:16:03 -0700 Subject: [PATCH 13/36] Properly escaping the schematic html attributes. --- djangoapps/simplewiki/mdx_circuit.py | 4 +++- static/js/CodeMirror/mitx_markdown.js | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/djangoapps/simplewiki/mdx_circuit.py b/djangoapps/simplewiki/mdx_circuit.py index 72e432012f..b598e184c6 100755 --- a/djangoapps/simplewiki/mdx_circuit.py +++ b/djangoapps/simplewiki/mdx_circuit.py @@ -10,6 +10,7 @@ import re import simplewiki.settings as settings +from django.utils.html import escape from mitxmako.shortcuts import render_to_response, render_to_string @@ -56,8 +57,9 @@ class CircuitPreprocessor(markdown.preprocessors.Preprocessor): class CircuitLink(markdown.inlinepatterns.Pattern): def handleMatch(self, m): data = m.group('data') + data = escape(data) ##TODO: We need to html escape the data - return etree.fromstring("") + return etree.fromstring("
") def makeExtension(configs=None) : diff --git a/static/js/CodeMirror/mitx_markdown.js b/static/js/CodeMirror/mitx_markdown.js index 9991a4f05c..9674d52264 100644 --- a/static/js/CodeMirror/mitx_markdown.js +++ b/static/js/CodeMirror/mitx_markdown.js @@ -58,20 +58,28 @@ CodeMirror.defineMode("mitx_markdown", function(cmCfg, modeCfg) { , strong = 'strong' , emstrong = 'emstrong'; + function escapeHtml(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } + var circuit_formatter = { creator: function(text) { var circuit_value = text.match(circuitRE)[1] - //TODO: We need real html escaping here - circuit_value = CodeMirror.htmlEscape(circuit_value);// circuit_value.replace("\"", "'"); + circuit_value = escapeHtml(circuit_value); var html = "" + - ""; + ""; return html; }, size: function(text) { - return {width: 150, height:154}; + return {width: 150, height:152}; }, callback: function(node, line) { update_schematics(); From d51341301ddf2d92b565c2ba094774c20593bea9 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 29 Mar 2012 15:56:21 -0700 Subject: [PATCH 14/36] Changed some css properties for proper sizing of elements. --- static/js/CodeMirror/mitx_markdown.js | 56 ++++++++++++++++----------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/static/js/CodeMirror/mitx_markdown.js b/static/js/CodeMirror/mitx_markdown.js index 9674d52264..6c0cb9dc26 100644 --- a/static/js/CodeMirror/mitx_markdown.js +++ b/static/js/CodeMirror/mitx_markdown.js @@ -1,8 +1,13 @@ +var schematic_height = 153; +var schematic_width = 400; + $(function(){ $(document).ready(function() { $("a[rel*=leanModal]").leanModal(); - $("body").append('
'); + $("body").append('
'+ + '' + + '
'); //This is the editor that pops up as a modal var editorCircuit = $("#schematic_editor").get(0); @@ -43,6 +48,7 @@ $(function(){ }); }); + CodeMirror.defineMode("mitx_markdown", function(cmCfg, modeCfg) { var htmlMode = CodeMirror.getMode(cmCfg, { name: 'xml', htmlMode: true }); @@ -66,30 +72,34 @@ CodeMirror.defineMode("mitx_markdown", function(cmCfg, modeCfg) { .replace(/"/g, """) .replace(/'/g, "'"); } - - var circuit_formatter = { - creator: function(text) { - var circuit_value = text.match(circuitRE)[1] + + var circuit_formatter = { + creator: function(text) { + var circuit_value = text.match(circuitRE)[1] - circuit_value = escapeHtml(circuit_value); + circuit_value = escapeHtml(circuit_value); - var html = "" + - ""; - - return html; - }, - size: function(text) { - return {width: 150, height:152}; - }, - callback: function(node, line) { - update_schematics(); - var schmInput = node.firstChild; - schmInput.codeMirrorLine = line; - schmInput.schematic.always_draw_grid = true; - schmInput.schematic.redraw_background(); - $(node).leanModal(); - } - }; + var html = ""; + + return html; + }, + size: function(text) { + return {width: schematic_width, height:schematic_height}; + }, + callback: function(node, line) { + update_schematics(); + var schmInput = node.firstChild.firstChild; + schmInput.codeMirrorLine = line; + if (schmInput.schematic) { //This is undefined if there was an error making the schematic + schmInput.schematic.canvas.style.display = "block"; //Otherwise, it gets line height and is a weird size + schmInput.schematic.always_draw_grid = true; + schmInput.schematic.redraw_background(); + } + $(node.firstChild).leanModal(); + } + }; + var hrRE = /^[*-=_]/ , ulRE = /^[*-+]\s+/ From 4938fb63dac39be0bd3c912d57dfdfcde75b2169 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 29 Mar 2012 16:39:06 -0700 Subject: [PATCH 15/36] Set size of wiki circuits --- djangoapps/simplewiki/mdx_circuit.py | 2 +- static/js/CodeMirror/mitx_markdown.js | 4 ++-- templates/simplewiki_edit.html | 9 ++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/djangoapps/simplewiki/mdx_circuit.py b/djangoapps/simplewiki/mdx_circuit.py index b598e184c6..ed1298e118 100755 --- a/djangoapps/simplewiki/mdx_circuit.py +++ b/djangoapps/simplewiki/mdx_circuit.py @@ -59,7 +59,7 @@ class CircuitLink(markdown.inlinepatterns.Pattern): data = m.group('data') data = escape(data) ##TODO: We need to html escape the data - return etree.fromstring("
") + return etree.fromstring("
") def makeExtension(configs=None) : diff --git a/static/js/CodeMirror/mitx_markdown.js b/static/js/CodeMirror/mitx_markdown.js index 6c0cb9dc26..a6f5ed14c6 100644 --- a/static/js/CodeMirror/mitx_markdown.js +++ b/static/js/CodeMirror/mitx_markdown.js @@ -1,5 +1,5 @@ -var schematic_height = 153; -var schematic_width = 400; +var schematic_height = 300; +var schematic_width = 500; $(function(){ $(document).ready(function() { diff --git a/templates/simplewiki_edit.html b/templates/simplewiki_edit.html index 85059a1766..87e53869fa 100644 --- a/templates/simplewiki_edit.html +++ b/templates/simplewiki_edit.html @@ -19,14 +19,13 @@ From 283ad48c25b4d16518de994fba0086da75edea0b Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 29 Mar 2012 16:41:44 -0700 Subject: [PATCH 16/36] Changed instructions for circuit-schematic: --- templates/simplewiki_instructions.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/simplewiki_instructions.html b/templates/simplewiki_instructions.html index 8b579feda2..58ef12a47c 100644 --- a/templates/simplewiki_instructions.html +++ b/templates/simplewiki_instructions.html @@ -2,7 +2,7 @@ This wiki uses Markdown for styling. There are several useful guides online.

MITx Additions: -

circuit:basic

+

circuit-schematic:

$LaTeX Math Expression$

To create a new wiki article, create a link to it. Clicking the link gives you the creation page.

[Article Name](wiki:ArticleName)

From d283bb57940e0e6ae8189eef67cd6d34d740471f Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 29 Mar 2012 18:00:42 -0700 Subject: [PATCH 17/36] Moved bug fix to minified leanModal --- static/js/jquery.leanModal.min.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/js/jquery.leanModal.min.js b/static/js/jquery.leanModal.min.js index e50d4edfd5..2ea21ad3df 100644 --- a/static/js/jquery.leanModal.min.js +++ b/static/js/jquery.leanModal.min.js @@ -1 +1,2 @@ -(function(a){a.fn.extend({leanModal:function(b){function e(b){a("#lean_overlay").fadeOut(200);a(b).css({display:"none"})}var c={top:100,overlay:.5,closeButton:".modal_close"};var d=a("
");a("body").append(d);b=a.extend(c,b);return this.each(function(){var c=b;a(this).click(function(b){var f=a(this).attr("href");a(".leanModal_box").css({display:"none"});a("body").append(d);a(".leanModal_box").append('');a("#lean_overlay").click(function(){e(f)});a(c.closeButton).click(function(){e(f)});var g=a(f).outerHeight();var h=a(f).outerWidth();a("#lean_overlay").css({display:"block",opacity:0});a("#lean_overlay").fadeTo(200,c.overlay);a(f).css({display:"block",position:"fixed",opacity:0,"z-index":11e3,left:50+"%","margin-left":-(h/2)+"px",top:c.top+"px"});var i=a(f).offset().top+"px";a(f).css({position:"absolute",top:i});a(f).fadeTo(200,1);b.preventDefault()})})}})})(jQuery) +(function(a){a.fn.extend({leanModal:function(c){function g(d){a("#lean_overlay").fadeOut(200);a(d).css({display:"none"})}var e=a("#lean_overlay");0==e.length&&(e=a("
"),a("body").append(e));c=a.extend({top:100,overlay:0.5,closeButton:".modal_close"},c);return this.each(function(){var d=c;a(this).click(function(c){var b=a(this).attr("href");a(".leanModal_box").css({display:"none"});a("body").append(e);a(".leanModal_box").append(''); +a("#lean_overlay").click(function(){g(b)});a(d.closeButton).click(function(){g(b)});a(b).outerHeight();var f=a(b).outerWidth();a("#lean_overlay").css({display:"block",opacity:0});a("#lean_overlay").fadeTo(200,d.overlay);a(b).css({display:"block",position:"fixed",opacity:0,"z-index":11E3,left:"50%","margin-left":-(f/2)+"px",top:d.top+"px"});f=a(b).offset().top+"px";a(b).css({position:"absolute",top:f});a(b).fadeTo(200,1);c.preventDefault()})})}})})(jQuery); \ No newline at end of file From d7383bf3c4b4eb35b84180998f656399e9b91f3e Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 29 Mar 2012 18:21:25 -0700 Subject: [PATCH 18/36] Small cleanups in codemirror. --- static/js/CodeMirror/codemirror.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index b20a7d033b..35d6c25103 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -617,6 +617,7 @@ var CodeMirror = (function() { function updateLines(from, to, newText, selFrom, selTo) { if (suppressEdits) return; + //This block ensures that widget lines don't have any text inserted on the same line. if (from.ch > 0 && (newText[0] != '' || newText.length == 1) && getLine(from.line).widgetFunction) { newText.unshift(''); var widgetLine = getLine(from.line); @@ -980,7 +981,7 @@ var CodeMirror = (function() { maxWidth = scroller.clientWidth; var curNode = lineDiv.firstChild, heightChanged = false; doc.iter(showingFrom, showingTo, function(line) { - if (!line.hidden && !line.widgetFunction) { + if (!line.hidden) { var height = Math.round(curNode.offsetHeight / th) || 1; if (line.widgetFunction) height = line.widgetFunction.size(line.text).height / textHeight(); if (line.height != height) { @@ -1053,7 +1054,6 @@ var CodeMirror = (function() { // This pass fills in the lines that actually changed. var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; var scratch = document.createElement("div"); - var text_height = textHeight(); //Remove this once heights are in pixels instead of lines doc.iter(from, to, function(line) { if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); if (!nextIntact || nextIntact.from > j) { @@ -2143,7 +2143,7 @@ var CodeMirror = (function() { }; return instance; }; - + // Utility functions for working with state. Exported because modes // sometimes need to do this. function copyState(mode, state) { @@ -2493,6 +2493,7 @@ var CodeMirror = (function() { if (!style) return null; return "cm-" + style.replace(/ +/g, " cm-"); } + if (!allText && endAt == null) span(" "); else if (!marked || !marked.length) From 183774f6376fb1e545138dd699617ad11f651b28 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 29 Mar 2012 22:27:55 -0700 Subject: [PATCH 19/36] Fixed codemirror arrowkey navigation bug. --- static/js/CodeMirror/codemirror.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/CodeMirror/codemirror.js b/static/js/CodeMirror/codemirror.js index 35d6c25103..ff127f80f6 100644 --- a/static/js/CodeMirror/codemirror.js +++ b/static/js/CodeMirror/codemirror.js @@ -1295,9 +1295,9 @@ var CodeMirror = (function() { if (goalColumn != null) pos.x = goalColumn; if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); else if (unit == "line") { - if (dir > 0) { - dist = Math.ceil(getLine(pos.line).height); - } else dist = 1; + var line = getLine(pos.line); + if (dir > 0 && line.widgetFunction) dist = Math.ceil(getLine(pos.line).height); + else dist = 1; dist *= textHeight(); } var target = coordsChar(loc.x, loc.y + dist * dir + 2); From acbe1e886071d166cffdbc0ec7dfc2cbba97f5dc Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 30 Mar 2012 05:49:32 -0700 Subject: [PATCH 20/36] Re-enabled unsaved changes confirmation on wiki edit. --- templates/simplewiki_edit.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/simplewiki_edit.html b/templates/simplewiki_edit.html index 87e53869fa..d5bd22cf8a 100644 --- a/templates/simplewiki_edit.html +++ b/templates/simplewiki_edit.html @@ -44,12 +44,12 @@ //Store the inital contents so we can compare for unsaved changes var initial_contents = editor.getValue(); - // window.onbeforeunload = function askConfirm() { //Warn the user before they navigate away - // if ( editor.getValue() != initial_contents ) { - // return "You have made changes to the article that have not been saved yet."; - // } - // }; - + window.onbeforeunload = function askConfirm() { //Warn the user before they navigate away + if ( editor.getValue() != initial_contents ) { + return "You have made changes to the article that have not been saved yet."; + } + }; + $("#submit_edit").click(function() { initial_contents = editor.getValue(); }); From 6f2f218bead79908a324697664af1d0ba23f5e0d Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 30 Mar 2012 06:03:10 -0700 Subject: [PATCH 21/36] The wiki creation page now uses the edit template, so it has the unsaved changes confirmation and Codemirror editor. --- djangoapps/simplewiki/views.py | 4 +++- templates/simplewiki_create.html | 24 ------------------------ templates/simplewiki_edit.html | 20 ++++++++++++++++---- 3 files changed, 19 insertions(+), 29 deletions(-) delete mode 100644 templates/simplewiki_create.html diff --git a/djangoapps/simplewiki/views.py b/djangoapps/simplewiki/views.py index 7b30b74907..7d743139ba 100644 --- a/djangoapps/simplewiki/views.py +++ b/djangoapps/simplewiki/views.py @@ -154,10 +154,11 @@ def create(request, wiki_url): d = {'wiki_form': f, 'wiki_write': True, + 'create_article' : True, } d.update(csrf(request)) - return render_to_response('simplewiki_create.html', d) + return render_to_response('simplewiki_edit.html', d) def edit(request, wiki_url): if not request.user.is_authenticated(): @@ -207,6 +208,7 @@ def edit(request, wiki_url): 'wiki_article': article, 'wiki_title' : article.title, 'wiki_attachments_write': article.can_attach(request.user), + 'create_article' : False, } d.update(csrf(request)) diff --git a/templates/simplewiki_create.html b/templates/simplewiki_create.html deleted file mode 100644 index 42b6bdc2cb..0000000000 --- a/templates/simplewiki_create.html +++ /dev/null @@ -1,24 +0,0 @@ -##This file is based on the template from the SimpleWiki source which carries the GPL license - -<%inherit file="simplewiki_base.html"/> - -<%block name="title">Wiki – Create Article – MITx 6.002x - -<%block name="wiki_page_title"> -

Create article

- - -<%block name="wiki_body"> -
-
- -
- - ${ wiki_form } - - -
- -<%include file="simplewiki_instructions.html"/> - - diff --git a/templates/simplewiki_edit.html b/templates/simplewiki_edit.html index d5bd22cf8a..11f275bfbc 100644 --- a/templates/simplewiki_edit.html +++ b/templates/simplewiki_edit.html @@ -2,22 +2,30 @@ <%inherit file="simplewiki_base.html"/> -<%block name="title">${"Edit " + wiki_title + " - " if wiki_title is not UNDEFINED else ""}MITx 6.002x Wiki +<%block name="title"> + +%if create_article: +Wiki – Create Article – MITx 6.002x +%else: +${"Edit " + wiki_title + " - " if wiki_title is not UNDEFINED else ""}MITx 6.002x Wiki +%endif + <%block name="wiki_page_title"> +%if create_article: +

Create article

+%else:

${ wiki_article.title }

+%endif <%block name="wiki_head"> - - - - - + <%block name="headextra"/> + + - -<%block name="headextra"/> - "> diff --git a/templates/simplewiki_base.html b/templates/simplewiki_base.html index 0f08d612c8..c31499c881 100644 --- a/templates/simplewiki_base.html +++ b/templates/simplewiki_base.html @@ -40,7 +40,7 @@ tex2jax: {inlineMath: [ ['$','$'], ["\\(","\\)"]], displayMath: [ ['$$','$$'], ["\\[","\\]"]]} }); - +