dev.sqlite
requirements.pybundle
*~
-*.orig
+*.swp
+.*.orig
/redakcja/media
/static
.sass-cache
django-gravatar2==1.4.2
django-extensions==2.1.6
django-bootstrap4==1.0.1
+libsasscompiler==0.1.5
sentry-sdk==0.12.2
orig_instance = Book.objects.get(pk=self.instance.pk)
old_gallery = orig_instance.gallery
new_gallery = self.cleaned_data['gallery']
- if new_gallery != old_gallery:
+ if new_gallery and old_gallery and new_gallery != old_gallery:
import shutil
import os.path
from django.conf import settings
<!--#include file='/pozor.html'-->
-<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
+<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<a class="navbar-brand" href="{% url 'catalogue_document_list' %}">
<img src="{% static "img/wl-orange.png" %}" alt="Platforma">
</a>
</ul>
<ul class="navbar-nav">
- {% include "catalogue/head_login.html" %}
+ {% include "registration/head_login.html" %}
</ul>
-
</nav>
<div class="container mt-4 mb-4">
-
-{% block content %}
-<div class="row">
-<div class="col-lg-8">
- {% block leftcolumn %}
- {% endblock leftcolumn %}
-</div>
-<div class="col-lg-4">
- {% block rightcolumn %}
- {% endblock rightcolumn %}
-</div>
+ {% block content %}
+ <div class="row">
+ <div class="col-lg-8">
+ {% block leftcolumn %}
+ {% endblock leftcolumn %}
+ </div>
+ <div class="col-lg-4">
+ {% block rightcolumn %}
+ {% endblock rightcolumn %}
+ </div>
+ </div>
+ {% endblock content %}
</div>
-{% endblock content %}
+<script
+ src="https://code.jquery.com/jquery-1.9.1.min.js"
+ integrity="sha256-wS9gmOZBqsqWxgIVgA8Y9WcQOa7PgSIX+rPA0VL2rbQ="
+ crossorigin="anonymous"></script>
+<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
-<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
{% javascript 'catalogue' %}
{% block add_js %}{% endblock %}
{% block extrabody %}
{% if book.single %}
{% with chunk as chunk %}
- <tr>
+ <tr class="table-sm">
<td><input type="checkbox" name="select_book" value="{{book.id}}" data-chunk-id="{{chunk.id}}"/></td>
- <td><a class="btn btn-sm btn-secondary" href="{% url 'catalogue_book' book.slug %}"><small>{% trans "Book settings" %}</small></a></td>
- <td><a class="btn btn-sm btn-secondary" href="{% url 'catalogue_chunk_edit' book.slug chunk.slug %}"><small>{% trans "Chunk settings" %}</small></a></td>
+ <td><a class="btn btn-sm btn-outline-secondary" href="{% url 'catalogue_book' book.slug %}" title="{% trans "Book settings" %}">📕</a></td>
+ <td><a class="btn btn-sm btn-outline-secondary" href="{% url 'catalogue_chunk_edit' book.slug chunk.slug %}" title="{% trans "Chunk settings" %}">📜</a></td>
<td><a class="btn btn-primary" target="_blank"
href="{% url 'wiki_editor' book.slug %}">
- {% trans "Edit:" %}
{{ book.title }}</a></td>
<td>{% if chunk.stage %}
{{ chunk.stage }}
{% endif %}</td>
<td class='user-column'>{% if chunk.user %}<a href="{% url 'catalogue_user' chunk.user.username %}">{{ chunk.user|username }}</a>{% endif %}</td>
<td>
- {% if chunk.published %}
- <div><small>{% trans "published" %}</small></div>
- {% endif %}
- {% if book.new_publishable %}
- <div><small>{% trans "publishable" %}</small></div>
- {% endif %}
- {% if chunk.changed %}
- <div><small>{% trans "changed" %}</small></div>
- {% endif %}
+ {% if book.published %}
+ <span class="badge badge-info" title="{% trans "published" %}">opubl.</span>
+ {% endif %}
+ {% if book.new_publishable %}
+ <span class="badge badge-primary" title="{% trans "publishable" %}">do publ.</span>
+ {% endif %}
+ {% if chunk.changed %}
+ <span class="badge badge-warning title="{% trans "changed" %}">zmiany</span>
+ {% endif %}
</td>
<td>{{ book.project.name }}</td>
</tr>
{% endwith %}
{% else %}
- <tr>
- <td><input type="checkbox" name="select_book" value="{{book.id}}"/></td>
- <td><a class='btn btn-sm btn-secondary' href="{% url 'catalogue_book' book.slug %}"><small>{% trans "Book settings" %}</small></a></td>
- <td></td>
- <td>{{ book.title }}</td>
- <td></td>
- <td class='user-column'></td>
- <td>
- {% if book.published %}
- <div><small>{% trans "published" %}</small></div>
- {% endif %}
- {% if book.new_publishable %}
- <div><small>{% trans "publishable" %}</small></div>
- {% endif %}
- </td>
- <td>{{ book.project.name }}</td>
+ <tr class="table-sm">
+ <td><input type="checkbox" name="select_book" value="{{book.id}}"/></td>
+ <td><a class='btn btn-sm btn-outline-secondary' href="{% url 'catalogue_book' book.slug %}" title="{% trans "Book settings" %}">📕</a></td>
+ <td></td>
+ <td>{{ book.title }}</td>
+ <td></td>
+ <td class='user-column'></td>
+ <td>
+ {% if book.published %}
+ <span class="badge badge-info" title="{% trans "published" %}">opubl.</span>
+ {% endif %}
+ {% if book.new_publishable %}
+ <span class="badge badge-info" title="{% trans "publishable" %}">do publ.</span>
+ {% endif %}
+ </td>
+ <td>{{ book.project.name }}</td>
</tr>
{% endif %}
<tr class="table-secondary table-sm">
<td><input type="checkbox" name="select_chunk" value="{{chunk.id}}" data-book-id="{{chunk.book.id}}" /></td>
<td class='book-settings-column'></td>
- <td><a class="btn btn-secondary btn-sm" href="{% url 'catalogue_chunk_edit' chunk.book.slug chunk.slug %}"><small>{% trans "Chunk settings" %}</small></a></td>
+ <td><a class="btn btn-outline-secondary btn-sm" href="{% url 'catalogue_chunk_edit' chunk.book.slug chunk.slug %}" title="{% trans "Chunk settings" %}">📜</a></td>
<td><a class="btn btn-primary" target="_blank" href="{{ chunk.get_absolute_url }}">
- {% trans "Edit:" %}
- {{ chunk.number }}.
- {{ chunk.title }}</a></td>
+ {{ chunk.number }}.
+ {{ chunk.title }}</a></td>
<td>{% if chunk.stage %}
- {{ chunk.stage }}
- {% else %}
- –
- {% endif %}</td>
- <td class='user-column'>{% if chunk.user %}
- <a href="{% url 'catalogue_user' chunk.user.username %}">
- {{ chunk.user|username }}
- </a>{% else %}
-
- {% endif %}</td>
+ {{ chunk.stage }}
+ {% else %}
+ –
+ {% endif %}</td>
+ <td class='user-column'>{% if chunk.user %}
+ <a href="{% url 'catalogue_user' chunk.user.username %}">
+ {{ chunk.user|username }}
+ </a>{% else %}
-</td>
-<td>
- {% if chunk.new_publishable %}
- <small>
- {% trans "publishable" %}
- </small>
- {% endif %}
- {% if chunk.changed %}
- <small>
- {% trans "changed" %}
- </small>
- {% endif %}
+ {% endif %}</td>
+
+ </td>
+ <td>
+ {% if chunk.changed %}
+ <span class="badge badge-warning title="{% trans "changed" %}">zmiany</span>
+ {% endif %}
</td>
<td></td>
</tr>
{% block add_js %}
-{% javascript 'book_list' %}
+ {% javascript 'book_list' %}
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.7.1/jquery.contextMenu.min.js"></script>
{% endblock %}
{% block add_css %}
-{% stylesheet 'book_list' %}
+ {% stylesheet 'book_list' %}
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.7.1/jquery.contextMenu.min.css">
{% endblock %}
{% block content %}
+++ /dev/null
-{% load i18n %}
-
-<li class="nav-item navbar-text">
-{% if user.is_authenticated %}
-{{ user.username }}
-</li>
-
-{% if user.is_staff %}
-<li class="nav-item">
- <a class="nav-link" href="{% url 'admin:index' %}">{% trans "Admin" %}</a>
-</li>
-{% endif %}
-
-<li class="nav-item">
- <a class="nav-link" href='{% url "logout" %}{% if logout_to %}?next={{ logout_to }}{% endif %}'>{% trans "Log Out" %}</a>
-</li>
-{% else %}
-{% url "cas_ng_login" as login_url %}
-{% ifnotequal login_url request.path %}
-<li class="nav-item">
-<a class="nav-link" href='{{ login_url }}'>{% trans "Log In" %}</a>
-</li>
-{% endifnotequal %}
-{% endif %}
{% block add_js %}
-{% javascript 'book_list' %}
+ {% javascript 'book_list' %}
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.7.1/jquery.contextMenu.min.js"></script>
{% endblock %}
{% block add_css %}
-{% stylesheet 'book_list' %}
+ {% stylesheet 'book_list' %}
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.7.1/jquery.contextMenu.min.css">
{% endblock %}
{% load i18n %}
{% load username from common_tags %}
-<tr>
+<tr class="table-sm">
<td><input type="checkbox" name="select_chunk" value="{{image.id}}"/></td>
- <td><a href="{% url 'catalogue_image' image.slug %}" class="btn btn-sm btn-secondary"><small>{% trans "Image settings" %}</small></a></td>
+ <td><a href="{% url 'catalogue_image' image.slug %}" class="btn btn-sm btn-secondary" title="{% trans "Image settings" %}">🖼</a></td>
<td><a class="btn btn-primary" target="_blank"
href="{% url 'wiki_img_editor' image.slug %}">
- {% trans "Edit:" %}
{{ image.title }}</a></td>
<td>{% if image.stage %}
{{ image.stage }}
{% endif %}</td>
<td class='user-column'>{% if image.user %}<a href="{% url 'catalogue_user' image.user.username %}">{{ image.user|username }}</a>{% endif %}</td>
<td>
- {% if image.published %}
- <div><small>{% trans "published" %}</small></div>
- {% endif %}
- {% if image.new_publishable %}
- <div><small>{% trans "publishable" %}</small></div>
- {% endif %}
- {% if image.changed %}
- <div><small>{% trans "changed" %}</small></div>
- {% endif %}
+ {% if image.published %}
+ <span class="badge badge-info" title="{% trans "published" %}">opubl.</span>
+ {% endif %}
+ {% if image.new_publishable %}
+ <span class="badge badge-primary" title="{% trans "publishable" %}">do publ.</span>
+ {% endif %}
+ {% if image.changed %}
+ <span class="badge badge-warning title="{% trans "changed" %}">zmiany</span>
+ {% endif %}
</td>
<td>{{ image.project.name }}</td>
</tr>
{% load pipeline %}
{% block add_js %}
-{% javascript 'book_list' %}
+ {% javascript 'book_list' %}
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.7.1/jquery.contextMenu.min.js"></script>
{% endblock %}
{% block add_css %}
-{% stylesheet 'book_list' %}
+ {% stylesheet 'book_list' %}
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.7.1/jquery.contextMenu.min.css">
{% endblock %}
{% block titleextra %}{% trans "My page" %}{% endblock %}
else:
revision = chunk.publishable()
if revision is None:
- raise Http404
+ revision = chunk.head
xml = revision.materialize().encode('utf-8')
try:
from .defaults import *
from ..localsettings import *
+DATA_UPLOAD_MAX_MEMORY_SIZE = 20000000
+
PROJECT_ROOT = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
STATICFILES_DIRS = [
PIPELINE = {
'CSS_COMPRESSOR': None,
'JS_COMPRESSOR': None,
+ 'COMPILERS': (
+ 'libsasscompiler.LibSassCompiler',
+ ),
# CSS and JS files to compress
'STYLESHEETS': {
'css/history.css',
'css/summary.css',
'css/html.css',
- 'css/jquery.autocomplete.css',
'css/imgareaselect-default.css',
'css/dialogs.css',
+
+ 'wiki/scss/splitter.scss',
+ 'wiki/scss/visual.scss'
),
'output_filename': 'compressed/detail_styles.css',
},
},
'book_list': {
'source_filenames': (
- 'contextmenu/jquery.contextMenu.css',
'css/book_list.css',
),
'output_filename': 'compressed/book_list.css',
'detail': {
'source_filenames': (
# libraries
- 'js/lib/jquery/jquery.autocomplete.js',
'js/lib/jquery/jquery.blockui.js',
'js/lib/jquery/jquery.elastic.js',
'js/lib/jquery/jquery.xmlns.js',
# wiki scripts
'js/wiki/wikiapi.js',
+ 'wiki/js/themes.js',
'js/wiki/xslt.js',
# base UI
'js/wiki/base.js',
+ 'wiki/js/sidebar-perspective.js',
'js/wiki/toolbar.js',
# dialogs
'wiki_img': {
'source_filenames': (
# libraries
- 'js/lib/jquery-1.4.2.min.js',
- 'js/lib/jquery/jquery.autocomplete.js',
'js/lib/jquery/jquery.blockui.js',
'js/lib/jquery/jquery.elastic.js',
'js/lib/jquery/jquery.imgareaselect.js',
# wiki scripts
'js/wiki_img/wikiapi.js',
+ 'wiki/js/themes.js',
# base UI
'js/wiki_img/base.js',
},
'book_list': {
'source_filenames': (
- 'contextmenu/jquery.ui.position.js',
- 'contextmenu/jquery.contextMenu.js',
'js/catalogue/book_list.js',
),
'output_filename': 'compressed/book_list.js',
}
+SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
+
+
try:
SENTRY_DSN
except NameError:
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\r
-\r
-<html xmlns="http://www.w3.org/1999/xhtml">\r
-\r
- <head>\r
- <title>jQuery Context Menu Plugin Demo</title>\r
- <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />\r
- \r
- <style type="text/css">\r
- BODY,\r
- HTML {\r
- padding: 0px;\r
- margin: 0px;\r
- }\r
- BODY {\r
- font-family: Verdana, Arial, Helvetica, sans-serif;\r
- font-size: 11px;\r
- background: #FFF;\r
- padding: 15px;\r
- }\r
- \r
- H1 {\r
- font-family: Georgia, serif;\r
- font-size: 20px;\r
- font-weight: normal;\r
- }\r
- \r
- H2 {\r
- font-family: Georgia, serif;\r
- font-size: 16px;\r
- font-weight: normal;\r
- margin: 0px 0px 10px 0px;\r
- }\r
- \r
- #myDiv {\r
- width: 150px;\r
- border: solid 1px #2AA7DE;\r
- background: #6CC8EF;\r
- text-align: center;\r
- padding: 4em .5em;\r
- margin: 1em;\r
- float: left;\r
- }\r
- \r
- #myList {\r
- margin: 1em;\r
- float: left;\r
- }\r
- \r
- #myList UL {\r
- padding: 0px;\r
- margin: 0em 1em;\r
- }\r
- \r
- #myList LI {\r
- width: 100px;\r
- border: solid 1px #2AA7DE;\r
- background: #6CC8EF;\r
- padding: 5px 5px;\r
- margin: 2px 0px;\r
- list-style: none;\r
- }\r
- \r
- #options {\r
- clear: left;\r
- }\r
- \r
- #options INPUT {\r
- font-family: Verdana, Arial, Helvetica, sans-serif;\r
- font-size: 11px;\r
- width: 150px;\r
- }\r
- \r
- </style> \r
- \r
- <script src="../js/lib/jquery-1.7.2.min.js" type="text/javascript"></script>\r
- <script src="jquery.contextMenu.js" type="text/javascript"></script>\r
- <link href="jquery.contextMenu.css" rel="stylesheet" type="text/css" />\r
- \r
- <script type="text/javascript">\r
- \r
- $(document).ready( function() {\r
- \r
- // Show menu when #myDiv is clicked\r
- $("#myDiv").contextMenu({\r
- menu: 'myMenu'\r
- },\r
- function(action, el, pos) {\r
- alert(\r
- 'Action: ' + action + '\n\n' +\r
- 'Element ID: ' + $(el).attr('id') + '\n\n' + \r
- 'X: ' + pos.x + ' Y: ' + pos.y + ' (relative to element)\n\n' + \r
- 'X: ' + pos.docX + ' Y: ' + pos.docY+ ' (relative to document)'\r
- );\r
- });\r
- \r
- // Show menu when a list item is clicked\r
- $("#myList UL LI").contextMenu({\r
- menu: 'myMenu'\r
- }, function(action, el, pos) {\r
- alert(\r
- 'Action: ' + action + '\n\n' +\r
- 'Element text: ' + $(el).text() + '\n\n' + \r
- 'X: ' + pos.x + ' Y: ' + pos.y + ' (relative to element)\n\n' + \r
- 'X: ' + pos.docX + ' Y: ' + pos.docY+ ' (relative to document)'\r
- );\r
- });\r
- \r
- // Disable menus\r
- $("#disableMenus").click( function() {\r
- $('#myDiv, #myList UL LI').disableContextMenu();\r
- $(this).attr('disabled', true);\r
- $("#enableMenus").attr('disabled', false);\r
- });\r
- \r
- // Enable menus\r
- $("#enableMenus").click( function() {\r
- $('#myDiv, #myList UL LI').enableContextMenu();\r
- $(this).attr('disabled', true);\r
- $("#disableMenus").attr('disabled', false);\r
- });\r
- \r
- // Disable cut/copy\r
- $("#disableItems").click( function() {\r
- $('#myMenu').disableContextMenuItems('#cut,#copy');\r
- $(this).attr('disabled', true);\r
- $("#enableItems").attr('disabled', false);\r
- });\r
- \r
- // Enable cut/copy\r
- $("#enableItems").click( function() {\r
- $('#myMenu').enableContextMenuItems('#cut,#copy');\r
- $(this).attr('disabled', true);\r
- $("#disableItems").attr('disabled', false);\r
- }); \r
- \r
- });\r
- \r
- </script>\r
- </head>\r
- \r
- <body>\r
- \r
- <h1>jQuery Context Menu Plugin Demo</h1>\r
- <p>\r
- This plugin lets you add context menu functionality to your web applications.\r
- </p>\r
- \r
- <p>\r
- <strong>Tip:</strong> Try using your keyboard to make a selection.\r
- </p>\r
- \r
- <p>\r
- <a href="http://abeautifulsite.net/2008/09/jquery-context-menu-plugin/">Back to the project page</a>\r
- </p>\r
- \r
- <h2>Demo</h2>\r
- \r
- <div id="myDiv">\r
- Right click to view the context menu\r
- </div>\r
- \r
- <div id="myList">\r
- <ul>\r
- <li>Item 1</li>\r
- <li>Item 2</li>\r
- <li>Item 3</li>\r
- <li>Item 4</li>\r
- <li>Item 5</li>\r
- <li>Item 6</li>\r
- </ul>\r
- </div>\r
- \r
- <div id="options">\r
- <p>\r
- <input type="button" id="disableItems" value="Disable Cut/Copy" />\r
- <input type="button" id="enableItems" value="Enable Cut/Copy" disabled="disabled" />\r
- </p>\r
- \r
- <p>\r
- <input type="button" id="disableMenus" value="Disable Context Menus" />\r
- <input type="button" id="enableMenus" value="Enable Context Menus" disabled="disabled" />\r
- </p>\r
- </div>\r
- \r
- <ul id="myMenu" class="contextMenu">\r
- <li class="edit"><a href="#edit">Edit</a></li>\r
- <li class="cut separator"><a href="#cut">Cut</a>\r
- <ul>\r
- <li class="pe"><a href="#pe">Pe</a></li>\r
- </ul>\r
- </li>\r
- <li class="copy"><a href="#copy">Copy</a></li>\r
- <li class="paste"><a href="#paste">Paste</a></li>\r
- <li class="delete"><a href="#delete">Delete</a></li>\r
- <li class="quit separator"><a href="#quit">Quit</a></li>\r
- </ul>\r
- \r
- </body>\r
-</html>\r
+++ /dev/null
-/*!\r
- * jQuery contextMenu - Plugin for simple contextMenu handling\r
- *\r
- * Version: 1.5.13\r
- *\r
- * Authors: Rodney Rehm, Addy Osmani (patches for FF)\r
- * Web: http://medialize.github.com/jQuery-contextMenu/\r
- *\r
- * Licensed under\r
- * MIT License http://www.opensource.org/licenses/mit-license\r
- * GPL v3 http://opensource.org/licenses/GPL-3.0\r
- *\r
- */\r
-\r
-.context-menu-list {\r
- margin:0; \r
- padding:0;\r
- \r
- min-width: 120px;\r
- max-width: 250px;\r
- display: inline-block;\r
- position: absolute;\r
- list-style-type: none;\r
- \r
- border: 1px solid #DDD;\r
- background: #EEE;\r
- \r
- -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);\r
- -moz-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);\r
- -ms-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);\r
- -o-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);\r
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);\r
- \r
- font-family: Verdana, Arial, Helvetica, sans-serif;\r
- font-size: 11px;\r
-}\r
-\r
-.context-menu-item {\r
- padding: 2px 2px 2px 24px;\r
- background-color: #EEE;\r
- position: relative;\r
- -moz-user-select: -moz-none;\r
-}\r
-\r
-.context-menu-separator {\r
- padding-bottom:0;\r
- border-bottom: 1px solid #DDD;\r
-}\r
-\r
-.context-menu-item > label {\r
- -moz-user-select: text;\r
-}\r
-\r
-.context-menu-item.hover {\r
- cursor: pointer;\r
- background-color: #39F;\r
-}\r
-\r
-.context-menu-item.disabled {\r
- color: #666;\r
-}\r
-\r
-.context-menu-input.hover,\r
-.context-menu-item.disabled.hover {\r
- cursor: default;\r
- background-color: #EEE;\r
-}\r
-\r
-.context-menu-submenu:after {\r
- content: ">";\r
- color: #666;\r
- position: absolute;\r
- top: 0;\r
- right: 3px;\r
- z-index: 1;\r
-}\r
-\r
-/* icons\r
- #protip:\r
- In case you want to use sprites for icons (which I would suggest you do) have a look at\r
- http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement \r
- .context-menu-item.icon:before {}\r
- */\r
-.context-menu-item.icon { min-height: 18px; background-repeat: no-repeat; background-position: 4px 2px; }\r
-.context-menu-item.icon-edit { background-image: url(images/page_white_edit.png); }\r
-.context-menu-item.icon-cut { background-image: url(images/cut.png); }\r
-.context-menu-item.icon-copy { background-image: url(images/page_white_copy.png); }\r
-.context-menu-item.icon-paste { background-image: url(images/page_white_paste.png); }\r
-.context-menu-item.icon-delete { background-image: url(images/page_white_delete.png); }\r
-.context-menu-item.icon-quit { background-image: url(images/door.png); }\r
-\r
-/* vertically align inside labels */\r
-.context-menu-input > label > * { vertical-align: top; }\r
-\r
-/* position checkboxes and radios as icons */\r
-.context-menu-input > label > input[type="checkbox"],\r
-.context-menu-input > label > input[type="radio"] {\r
- margin-left: -17px;\r
-}\r
-.context-menu-input > label > span {\r
- margin-left: 5px;\r
-}\r
-\r
-.context-menu-input > label,\r
-.context-menu-input > label > input[type="text"],\r
-.context-menu-input > label > textarea,\r
-.context-menu-input > label > select {\r
- display: block;\r
- width: 100%;\r
- \r
- -webkit-box-sizing: border-box;\r
- -moz-box-sizing: border-box;\r
- -ms-box-sizing: border-box;\r
- -o-box-sizing: border-box;\r
- box-sizing: border-box;\r
-}\r
-\r
-.context-menu-input > label > textarea {\r
- height: 100px;\r
-}\r
-.context-menu-item > .context-menu-list {\r
- display: none;\r
- /* re-positioned by js */\r
- right: -5px;\r
- top: 5px;\r
-}\r
-\r
-.context-menu-item.hover > .context-menu-list {\r
- display: block;\r
-}\r
-\r
-.context-menu-accesskey {\r
- text-decoration: underline;\r
-}\r
+++ /dev/null
-/*!\r
- * jQuery contextMenu - Plugin for simple contextMenu handling\r
- *\r
- * Version: 1.5.13\r
- *\r
- * Authors: Rodney Rehm, Addy Osmani (patches for FF)\r
- * Web: http://medialize.github.com/jQuery-contextMenu/\r
- *\r
- * Licensed under\r
- * MIT License http://www.opensource.org/licenses/mit-license\r
- * GPL v3 http://opensource.org/licenses/GPL-3.0\r
- *\r
- */\r
-\r
-(function($, undefined){\r
- \r
- // TODO: -\r
- // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio\r
- // create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative\r
-\r
-// determine html5 compatibility\r
-$.support.htmlMenuitem = ('HTMLMenuItemElement' in window);\r
-$.support.htmlCommand = ('HTMLCommandElement' in window);\r
-\r
-var // currently active contextMenu trigger\r
- $currentTrigger = null,\r
- // is contextMenu initialized with at least one menu?\r
- initialized = false,\r
- // window handle\r
- $win = $(window),\r
- // number of registered menus\r
- counter = 0,\r
- // mapping selector to namespace\r
- namespaces = {},\r
- // mapping namespace to options\r
- menus = {},\r
- // custom command type handlers\r
- types = {},\r
- // default values\r
- defaults = {\r
- // selector of contextMenu trigger\r
- selector: null,\r
- // where to append the menu to\r
- appendTo: null,\r
- // method to trigger context menu ["right", "left", "hover"]\r
- trigger: "right",\r
- // hide menu when mouse leaves trigger / menu elements\r
- autoHide: false,\r
- // ms to wait before showing a hover-triggered context menu\r
- delay: 200,\r
- // determine position to show menu at\r
- determinePosition: function($menu) {\r
- // position to the lower middle of the trigger element\r
- if ($.ui && $.ui.position) {\r
- // .position() is provided as a jQuery UI utility\r
- // (...and it won't work on hidden elements)\r
- $menu.css('display', 'block').position({\r
- my: "center top",\r
- at: "center bottom",\r
- of: this,\r
- offset: "0 5",\r
- collision: "fit"\r
- }).css('display', 'none');\r
- } else {\r
- // determine contextMenu position\r
- var offset = this.offset();\r
- offset.top += this.outerHeight();\r
- offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2;\r
- $menu.css(offset);\r
- }\r
- },\r
- // position menu\r
- position: function(opt, x, y) {\r
- var $this = this,\r
- offset;\r
- // determine contextMenu position\r
- if (!x && !y) {\r
- opt.determinePosition.call(this, opt.$menu);\r
- return;\r
- } else if (x === "maintain" && y === "maintain") {\r
- // x and y must not be changed (after re-show on command click)\r
- offset = opt.$menu.position();\r
- } else {\r
- // x and y are given (by mouse event)\r
- var triggerIsFixed = opt.$trigger.parents().andSelf()\r
- .filter(function() {\r
- return $(this).css('position') == "fixed";\r
- }).length;\r
-\r
- if (triggerIsFixed) {\r
- y -= $win.scrollTop();\r
- x -= $win.scrollLeft();\r
- }\r
- offset = {top: y, left: x};\r
- }\r
- \r
- // correct offset if viewport demands it\r
- var bottom = $win.scrollTop() + $win.height(),\r
- right = $win.scrollLeft() + $win.width(),\r
- height = opt.$menu.height(),\r
- width = opt.$menu.width();\r
- \r
- if (offset.top + height > bottom) {\r
- offset.top -= height;\r
- }\r
- \r
- if (offset.left + width > right) {\r
- offset.left -= width;\r
- }\r
- \r
- opt.$menu.css(offset);\r
- },\r
- // position the sub-menu\r
- positionSubmenu: function($menu) {\r
- if ($.ui && $.ui.position) {\r
- // .position() is provided as a jQuery UI utility\r
- // (...and it won't work on hidden elements)\r
- $menu.css('display', 'block').position({\r
- my: "left top",\r
- at: "right top",\r
- of: this,\r
- collision: "fit"\r
- }).css('display', '');\r
- } else {\r
- // determine contextMenu position\r
- var offset = this.offset();\r
- offset.top += 0;\r
- offset.left += this.outerWidth();\r
- $menu.css(offset);\r
- }\r
- },\r
- // offset to add to zIndex\r
- zIndex: 1,\r
- // show hide animation settings\r
- animation: {\r
- duration: 50,\r
- show: 'slideDown',\r
- hide: 'slideUp'\r
- },\r
- // events\r
- events: {\r
- show: $.noop,\r
- hide: $.noop\r
- },\r
- // default callback\r
- callback: null,\r
- // list of contextMenu items\r
- items: {}\r
- },\r
- // mouse position for hover activation\r
- hoveract = {\r
- timer: null,\r
- pageX: null,\r
- pageY: null\r
- },\r
- // determine zIndex\r
- zindex = function($t) {\r
- var zin = 0,\r
- $tt = $t;\r
-\r
- while (true) {\r
- zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0);\r
- $tt = $tt.parent();\r
- if (!$tt || !$tt.length || $tt.prop('nodeName').toLowerCase() == 'body') {\r
- break;\r
- }\r
- }\r
- \r
- return zin;\r
- },\r
- // event handlers\r
- handle = {\r
- // abort anything\r
- abortevent: function(e){\r
- e.preventDefault();\r
- e.stopImmediatePropagation();\r
- },\r
- \r
- // contextmenu show dispatcher\r
- contextmenu: function(e) {\r
- var $this = $(this);\r
- \r
- // disable actual context-menu\r
- e.preventDefault();\r
- e.stopImmediatePropagation();\r
- \r
- // abort native-triggered events unless we're triggering on right click\r
- if (e.data.trigger != 'right' && e.originalEvent) {\r
- return;\r
- }\r
- \r
- if (!$this.hasClass('context-menu-disabled')) {\r
- // theoretically need to fire a show event at <menu>\r
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus\r
- // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this });\r
- // e.data.$menu.trigger(evt);\r
- \r
- $currentTrigger = $this;\r
- if (e.data.build) {\r
- var built = e.data.build($currentTrigger, e);\r
- // abort if build() returned false\r
- if (built === false) {\r
- return;\r
- }\r
- \r
- // dynamically build menu on invocation\r
- e.data = $.extend(true, defaults, e.data, built || {});\r
-\r
- // abort if there are no items to display\r
- if (!e.data.items || $.isEmptyObject(e.data.items)) {\r
- // Note: jQuery captures and ignores errors from event handlers\r
- if (window.console) {\r
- (console.error || console.log)("No items specified to show in contextMenu");\r
- }\r
- \r
- throw new Error('No Items sepcified');\r
- }\r
- \r
- // backreference for custom command type creation\r
- e.data.$trigger = $currentTrigger;\r
- \r
- op.create(e.data);\r
- }\r
- // show menu\r
- op.show.call($this, e.data, e.pageX, e.pageY);\r
- }\r
- },\r
- // contextMenu left-click trigger\r
- click: function(e) {\r
- e.preventDefault();\r
- e.stopImmediatePropagation();\r
- $(this).trigger(jQuery.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));\r
- },\r
- // contextMenu right-click trigger\r
- mousedown: function(e) {\r
- // register mouse down\r
- var $this = $(this);\r
- \r
- // hide any previous menus\r
- if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) {\r
- $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide');\r
- }\r
- \r
- // activate on right click\r
- if (e.button == 2) {\r
- $currentTrigger = $this.data('contextMenuActive', true);\r
- }\r
- },\r
- // contextMenu right-click trigger\r
- mouseup: function(e) {\r
- // show menu\r
- var $this = $(this);\r
- if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) {\r
- e.preventDefault();\r
- e.stopImmediatePropagation();\r
- $currentTrigger = $this;\r
- $this.trigger(jQuery.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));\r
- }\r
- \r
- $this.removeData('contextMenuActive');\r
- },\r
- // contextMenu hover trigger\r
- mouseenter: function(e) {\r
- var $this = $(this),\r
- $related = $(e.relatedTarget),\r
- $document = $(document);\r
- \r
- // abort if we're coming from a menu\r
- if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {\r
- return;\r
- }\r
- \r
- // abort if a menu is shown\r
- if ($currentTrigger && $currentTrigger.length) {\r
- return;\r
- }\r
- \r
- hoveract.pageX = e.pageX;\r
- hoveract.pageY = e.pageY;\r
- hoveract.data = e.data;\r
- $document.on('mousemove.contextMenuShow', handle.mousemove);\r
- hoveract.timer = setTimeout(function() {\r
- hoveract.timer = null;\r
- $document.off('mousemove.contextMenuShow');\r
- $currentTrigger = $this;\r
- $this.trigger(jQuery.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY }));\r
- }, e.data.delay );\r
- },\r
- // contextMenu hover trigger\r
- mousemove: function(e) {\r
- hoveract.pageX = e.pageX;\r
- hoveract.pageY = e.pageY;\r
- },\r
- // contextMenu hover trigger\r
- mouseleave: function(e) {\r
- // abort if we're leaving for a menu\r
- var $related = $(e.relatedTarget);\r
- if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {\r
- return;\r
- }\r
- \r
- try {\r
- clearTimeout(hoveract.timer);\r
- } catch(e) {}\r
- \r
- hoveract.timer = null;\r
- },\r
- \r
- // click on layer to hide contextMenu\r
- layerClick: function(e) {\r
- var $this = $(this),\r
- root = $this.data('contextMenuRoot');\r
- \r
- e.preventDefault();\r
- e.stopImmediatePropagation();\r
- \r
- if ((root.trigger == 'left' && e.button == 0) || (root.trigger == 'right' && e.button == 2)) {\r
- var offset = root.$trigger.offset();\r
- \r
- // while this looks kinda awful, it's the best way to avoid\r
- // unnecessarily calculating any positions\r
- offset.top += $(window).scrollTop();\r
- if (offset.top <= e.pageY) {\r
- offset.left += $(window).scrollLeft();\r
- if (offset.left <= e.pageX) {\r
- offset.bottom = offset.top + root.$trigger.outerHeight();\r
- if (offset.bottom >= e.pageY) {\r
- offset.right = offset.left + root.$trigger.outerWidth();\r
- if (offset.right >= e.pageX) {\r
- // reposition\r
- root.position.call(root.$trigger, root, e.pageX, e.pageY);\r
- return;\r
- }\r
- }\r
- }\r
- }\r
- } \r
- \r
- // remove only after mouseup has completed\r
- $this.on('mouseup', function(e) {\r
- e.preventDefault();\r
- e.stopImmediatePropagation();\r
- root.$menu.trigger('contextmenu:hide');\r
- });\r
- },\r
- // key handled :hover\r
- keyStop: function(e, opt) {\r
- if (!opt.isInput) {\r
- e.preventDefault();\r
- }\r
- \r
- e.stopPropagation();\r
- },\r
- key: function(e) {\r
- var opt = $currentTrigger.data('contextMenu') || {},\r
- $children = opt.$menu.children(),\r
- $round;\r
-\r
- switch (e.keyCode) {\r
- case 9:\r
- case 38: // up\r
- handle.keyStop(e, opt);\r
- // if keyCode is [38 (up)] or [9 (tab) with shift]\r
- if (opt.isInput) {\r
- if (e.keyCode == 9 && e.shiftKey) {\r
- e.preventDefault();\r
- opt.$selected && opt.$selected.find('input, textarea, select').blur();\r
- opt.$menu.trigger('prevcommand');\r
- return;\r
- } else if (e.keyCode == 38 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {\r
- // checkboxes don't capture this key\r
- e.preventDefault();\r
- return;\r
- }\r
- } else if (e.keyCode != 9 || e.shiftKey) {\r
- opt.$menu.trigger('prevcommand');\r
- return;\r
- }\r
- \r
- case 9: // tab\r
- case 40: // down\r
- handle.keyStop(e, opt);\r
- if (opt.isInput) {\r
- if (e.keyCode == 9) {\r
- e.preventDefault();\r
- opt.$selected && opt.$selected.find('input, textarea, select').blur();\r
- opt.$menu.trigger('nextcommand');\r
- return;\r
- } else if (e.keyCode == 40 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {\r
- // checkboxes don't capture this key\r
- e.preventDefault();\r
- return;\r
- }\r
- } else {\r
- opt.$menu.trigger('nextcommand');\r
- return;\r
- }\r
- break;\r
- \r
- case 37: // left\r
- handle.keyStop(e, opt);\r
- if (opt.isInput || !opt.$selected || !opt.$selected.length) {\r
- break;\r
- }\r
- \r
- if (!opt.$selected.parent().hasClass('context-menu-root')) {\r
- var $parent = opt.$selected.parent().parent();\r
- opt.$selected.trigger('contextmenu:blur');\r
- opt.$selected = $parent;\r
- return;\r
- }\r
- break;\r
- \r
- case 39: // right\r
- handle.keyStop(e, opt);\r
- if (opt.isInput || !opt.$selected || !opt.$selected.length) {\r
- break;\r
- }\r
- \r
- var itemdata = opt.$selected.data('contextMenu') || {};\r
- if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) {\r
- opt.$selected = null;\r
- itemdata.$selected = null;\r
- itemdata.$menu.trigger('nextcommand');\r
- return;\r
- }\r
- break;\r
- \r
- case 35: // end\r
- case 36: // home\r
- if (opt.$selected && opt.$selected.find('input, textarea, select').length) {\r
- return;\r
- } else {\r
- (opt.$selected && opt.$selected.parent() || opt.$menu)\r
- .children(':not(.disabled, .not-selectable)')[e.keyCode == 36 ? 'first' : 'last']()\r
- .trigger('contextmenu:focus');\r
- e.preventDefault();\r
- return;\r
- }\r
- break;\r
- \r
- case 13: // enter\r
- handle.keyStop(e, opt);\r
- if (opt.isInput) {\r
- if (opt.$selected && !opt.$selected.is('textarea, select')) {\r
- e.preventDefault();\r
- return;\r
- }\r
- break;\r
- }\r
- opt.$selected && opt.$selected.trigger('mouseup');\r
- return;\r
- \r
- case 32: // space\r
- case 33: // page up\r
- case 34: // page down\r
- // prevent browser from scrolling down while menu is visible\r
- handle.keyStop(e, opt);\r
- return;\r
- \r
- case 27: // esc\r
- handle.keyStop(e, opt);\r
- opt.$menu.trigger('contextmenu:hide');\r
- return;\r
- \r
- default: // 0-9, a-z\r
- var k = (String.fromCharCode(e.keyCode)).toUpperCase();\r
- if (opt.accesskeys[k]) {\r
- // according to the specs accesskeys must be invoked immediately\r
- opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu\r
- ? 'contextmenu:focus'\r
- : 'mouseup'\r
- );\r
- return;\r
- }\r
- break;\r
- }\r
- // pass event to selected item, \r
- // stop propagation to avoid endless recursion\r
- e.stopPropagation();\r
- opt.$selected && opt.$selected.trigger(e);\r
- },\r
-\r
- // select previous possible command in menu\r
- prevItem: function(e) {\r
- e.stopPropagation();\r
- var opt = $(this).data('contextMenu') || {};\r
-\r
- // obtain currently selected menu\r
- if (opt.$selected) {\r
- var $s = opt.$selected;\r
- opt = opt.$selected.parent().data('contextMenu') || {};\r
- opt.$selected = $s;\r
- }\r
- \r
- var $children = opt.$menu.children(),\r
- $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(),\r
- $round = $prev;\r
- \r
- // skip disabled\r
- while ($prev.hasClass('disabled') || $prev.hasClass('not-selectable')) {\r
- if ($prev.prev().length) {\r
- $prev = $prev.prev();\r
- } else {\r
- $prev = $children.last();\r
- }\r
- if ($prev.is($round)) {\r
- // break endless loop\r
- return;\r
- }\r
- }\r
- \r
- // leave current\r
- if (opt.$selected) {\r
- handle.itemMouseleave.call(opt.$selected.get(0), e);\r
- }\r
- \r
- // activate next\r
- handle.itemMouseenter.call($prev.get(0), e);\r
- \r
- // focus input\r
- var $input = $prev.find('input, textarea, select');\r
- if ($input.length) {\r
- $input.focus();\r
- }\r
- },\r
- // select next possible command in menu\r
- nextItem: function(e) {\r
- e.stopPropagation();\r
- var opt = $(this).data('contextMenu') || {};\r
-\r
- // obtain currently selected menu\r
- if (opt.$selected) {\r
- var $s = opt.$selected;\r
- opt = opt.$selected.parent().data('contextMenu') || {};\r
- opt.$selected = $s;\r
- }\r
-\r
- var $children = opt.$menu.children(),\r
- $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(),\r
- $round = $next;\r
-\r
- // skip disabled\r
- while ($next.hasClass('disabled') || $next.hasClass('not-selectable')) {\r
- if ($next.next().length) {\r
- $next = $next.next();\r
- } else {\r
- $next = $children.first();\r
- }\r
- if ($next.is($round)) {\r
- // break endless loop\r
- return;\r
- }\r
- }\r
- \r
- // leave current\r
- if (opt.$selected) {\r
- handle.itemMouseleave.call(opt.$selected.get(0), e);\r
- }\r
- \r
- // activate next\r
- handle.itemMouseenter.call($next.get(0), e);\r
- \r
- // focus input\r
- var $input = $next.find('input, textarea, select');\r
- if ($input.length) {\r
- $input.focus();\r
- }\r
- },\r
- \r
- // flag that we're inside an input so the key handler can act accordingly\r
- focusInput: function(e) {\r
- var $this = $(this).closest('.context-menu-item'),\r
- data = $this.data(),\r
- opt = data.contextMenu,\r
- root = data.contextMenuRoot;\r
-\r
- root.$selected = opt.$selected = $this;\r
- root.isInput = opt.isInput = true;\r
- },\r
- // flag that we're inside an input so the key handler can act accordingly\r
- blurInput: function(e) {\r
- var $this = $(this).closest('.context-menu-item'),\r
- data = $this.data(),\r
- opt = data.contextMenu,\r
- root = data.contextMenuRoot;\r
-\r
- root.isInput = opt.isInput = false;\r
- },\r
- \r
- // :hover on menu\r
- menuMouseenter: function(e) {\r
- var root = $(this).data().contextMenuRoot;\r
- root.hovering = true;\r
- },\r
- // :hover on menu\r
- menuMouseleave: function(e) {\r
- var root = $(this).data().contextMenuRoot;\r
- if (root.$layer && root.$layer.is(e.relatedTarget)) {\r
- root.hovering = false;\r
- }\r
- },\r
- \r
- // :hover done manually so key handling is possible\r
- itemMouseenter: function(e) {\r
- var $this = $(this),\r
- data = $this.data(),\r
- opt = data.contextMenu,\r
- root = data.contextMenuRoot;\r
- \r
- root.hovering = true;\r
-\r
- // abort if we're re-entering\r
- if (e && root.$layer && root.$layer.is(e.relatedTarget)) {\r
- e.preventDefault();\r
- e.stopImmediatePropagation();\r
- }\r
-\r
- // make sure only one item is selected\r
- (opt.$menu ? opt : root).$menu\r
- .children('.hover').trigger('contextmenu:blur');\r
-\r
- if ($this.hasClass('disabled') || $this.hasClass('not-selectable')) {\r
- opt.$selected = null;\r
- return;\r
- }\r
- \r
- $this.trigger('contextmenu:focus');\r
- },\r
- // :hover done manually so key handling is possible\r
- itemMouseleave: function(e) {\r
- var $this = $(this),\r
- data = $this.data(),\r
- opt = data.contextMenu,\r
- root = data.contextMenuRoot;\r
-\r
- if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) {\r
- root.$selected && root.$selected.trigger('contextmenu:blur');\r
- e.preventDefault();\r
- e.stopImmediatePropagation();\r
- root.$selected = opt.$selected = opt.$node;\r
- return;\r
- }\r
- \r
- $this.trigger('contextmenu:blur');\r
- },\r
- // contextMenu item click\r
- itemClick: function(e) {\r
- var $this = $(this),\r
- data = $this.data(),\r
- opt = data.contextMenu,\r
- root = data.contextMenuRoot,\r
- key = data.contextMenuKey,\r
- callback;\r
-\r
- // abort if the key is unknown or disabled or is a menu\r
- if (!opt.items[key] || $this.hasClass('disabled') || $this.hasClass('context-menu-submenu')) {\r
- return;\r
- }\r
-\r
- e.preventDefault();\r
- e.stopImmediatePropagation();\r
-\r
- if ($.isFunction(root.callbacks[key])) {\r
- // item-specific callback\r
- callback = root.callbacks[key];\r
- } else if ($.isFunction(root.callback)) {\r
- // default callback\r
- callback = root.callback; \r
- } else {\r
- // no callback, no action\r
- return;\r
- }\r
-\r
- // hide menu if callback doesn't stop that\r
- if (callback.call(root.$trigger, key, root) !== false) {\r
- root.$menu.trigger('contextmenu:hide');\r
- } else {\r
- op.update.call(root.$trigger, root);\r
- }\r
- },\r
- // ignore click events on input elements\r
- inputClick: function(e) {\r
- e.stopImmediatePropagation();\r
- },\r
- \r
- // hide <menu>\r
- hideMenu: function(e) {\r
- var root = $(this).data('contextMenuRoot');\r
- op.hide.call(root.$trigger, root);\r
- },\r
- // focus <command>\r
- focusItem: function(e) {\r
- e.stopPropagation();\r
- var $this = $(this),\r
- data = $this.data(),\r
- opt = data.contextMenu,\r
- root = data.contextMenuRoot;\r
-\r
- $this.addClass('hover')\r
- .siblings('.hover').trigger('contextmenu:blur');\r
- \r
- // remember selected\r
- opt.$selected = root.$selected = $this;\r
- \r
- // position sub-menu - do after show so dumb $.ui.position can keep up\r
- if (opt.$node) {\r
- root.positionSubmenu.call(opt.$node, opt.$menu);\r
- }\r
- },\r
- // blur <command>\r
- blurItem: function(e) {\r
- e.stopPropagation();\r
- var $this = $(this),\r
- data = $this.data(),\r
- opt = data.contextMenu,\r
- root = data.contextMenuRoot;\r
- \r
- $this.removeClass('hover');\r
- opt.$selected = null;\r
- }\r
- },\r
- // operations\r
- op = {\r
- show: function(opt, x, y) {\r
- var $this = $(this),\r
- offset,\r
- css = {};\r
-\r
- // hide any open menus\r
- $('#context-menu-layer').trigger('mousedown');\r
-\r
- // backreference for callbacks\r
- opt.$trigger = $this;\r
-\r
- // show event\r
- if (opt.events.show.call($this, opt) === false) {\r
- $currentTrigger = null;\r
- return;\r
- }\r
- \r
- // create or update context menu\r
- op.update.call($this, opt);\r
- \r
- // position menu\r
- opt.position.call($this, opt, x, y);\r
-\r
- // make sure we're in front\r
- if (opt.zIndex) {\r
- css.zIndex = zindex($this) + opt.zIndex;\r
- }\r
- \r
- // add layer\r
- op.layer.call(opt.$menu, opt, css.zIndex);\r
- \r
- // adjust sub-menu zIndexes\r
- opt.$menu.find('ul').css('zIndex', css.zIndex + 1);\r
- \r
- // position and show context menu\r
- opt.$menu.css( css )[opt.animation.show](opt.animation.duration);\r
- // make options available\r
- $this.data('contextMenu', opt);\r
- // register key handler\r
- $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key);\r
- // register autoHide handler\r
- if (opt.autoHide) {\r
- // trigger element coordinates\r
- var pos = $this.position();\r
- pos.right = pos.left + $this.outerWidth();\r
- pos.bottom = pos.top + this.outerHeight();\r
- // mouse position handler\r
- $(document).on('mousemove.contextMenuAutoHide', function(e) {\r
- if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) {\r
- // if mouse in menu...\r
- opt.$layer.trigger('mousedown');\r
- }\r
- });\r
- }\r
- },\r
- hide: function(opt) {\r
- var $this = $(this);\r
- if (!opt) {\r
- opt = $this.data('contextMenu') || {};\r
- }\r
- \r
- // hide event\r
- if (opt.events && opt.events.hide.call($this, opt) === false) {\r
- return;\r
- }\r
- \r
- if (opt.$layer) {\r
- // keep layer for a bit so the contextmenu event can be aborted properly by opera\r
- setTimeout((function($layer){ return function(){\r
- $layer.remove();\r
- };\r
- })(opt.$layer), 10);\r
- \r
- try {\r
- delete opt.$layer;\r
- } catch(e) {\r
- opt.$layer = null;\r
- }\r
- }\r
- \r
- // remove handle\r
- $currentTrigger = null;\r
- // remove selected\r
- opt.$menu.find('.hover').trigger('contextmenu:blur');\r
- opt.$selected = null;\r
- // unregister key and mouse handlers\r
- //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705\r
- $(document).off('.contextMenuAutoHide').off('keydown.contextMenu');\r
- // hide menu\r
- opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration);\r
- \r
- // tear down dynamically built menu\r
- if (opt.build) {\r
- opt.$menu.remove();\r
- $.each(opt, function(key, value) {\r
- switch (key) {\r
- case 'ns':\r
- case 'selector':\r
- case 'build':\r
- case 'trigger':\r
- return true;\r
-\r
- default:\r
- opt[key] = undefined;\r
- try {\r
- delete opt[key];\r
- } catch (e) {}\r
- return true;\r
- }\r
- });\r
- }\r
- },\r
- create: function(opt, root) {\r
- if (root === undefined) {\r
- root = opt;\r
- }\r
- // create contextMenu\r
- opt.$menu = $('<ul class="context-menu-list ' + (opt.className || "") + '"></ul>').data({\r
- 'contextMenu': opt,\r
- 'contextMenuRoot': root\r
- });\r
- \r
- $.each(['callbacks', 'commands', 'inputs'], function(i,k){\r
- opt[k] = {};\r
- if (!root[k]) {\r
- root[k] = {};\r
- }\r
- });\r
- \r
- root.accesskeys || (root.accesskeys = {});\r
- \r
- // create contextMenu items\r
- $.each(opt.items, function(key, item){\r
- var $t = $('<li class="context-menu-item ' + (item.className || "") +'"></li>'),\r
- $label = null,\r
- $input = null;\r
- \r
- item.$node = $t.data({\r
- 'contextMenu': opt,\r
- 'contextMenuRoot': root,\r
- 'contextMenuKey': key\r
- });\r
- \r
- // register accesskey\r
- // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that\r
- if (item.accesskey) {\r
- var aks = splitAccesskey(item.accesskey);\r
- for (var i=0, ak; ak = aks[i]; i++) {\r
- if (!root.accesskeys[ak]) {\r
- root.accesskeys[ak] = item;\r
- item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '<span class="context-menu-accesskey">$1</span>');\r
- break;\r
- }\r
- }\r
- }\r
- \r
- if (typeof item == "string") {\r
- $t.addClass('context-menu-separator not-selectable');\r
- } else if (item.type && types[item.type]) {\r
- // run custom type handler\r
- types[item.type].call($t, item, opt, root);\r
- // register commands\r
- $.each([opt, root], function(i,k){\r
- k.commands[key] = item;\r
- if ($.isFunction(item.callback)) {\r
- k.callbacks[key] = item.callback;\r
- }\r
- });\r
- } else {\r
- // add label for input\r
- if (item.type == 'html') {\r
- $t.addClass('context-menu-html not-selectable');\r
- } else if (item.type) {\r
- $label = $('<label></label>').appendTo($t);\r
- $('<span></span>').html(item._name || item.name).appendTo($label);\r
- $t.addClass('context-menu-input');\r
- opt.hasTypes = true;\r
- $.each([opt, root], function(i,k){\r
- k.commands[key] = item;\r
- k.inputs[key] = item;\r
- });\r
- } else if (item.items) {\r
- item.type = 'sub';\r
- }\r
- \r
- switch (item.type) {\r
- case 'text':\r
- $input = $('<input type="text" value="1" name="context-menu-input-'+ key +'" value="">')\r
- .val(item.value || "").appendTo($label);\r
- break;\r
- \r
- case 'textarea':\r
- $input = $('<textarea name="context-menu-input-'+ key +'"></textarea>')\r
- .val(item.value || "").appendTo($label);\r
-\r
- if (item.height) {\r
- $input.height(item.height);\r
- }\r
- break;\r
-\r
- case 'checkbox':\r
- $input = $('<input type="checkbox" value="1" name="context-menu-input-'+ key +'" value="">')\r
- .val(item.value || "").prop("checked", !!item.selected).prependTo($label);\r
- break;\r
-\r
- case 'radio':\r
- $input = $('<input type="radio" value="1" name="context-menu-input-'+ item.radio +'" value="">')\r
- .val(item.value || "").prop("checked", !!item.selected).prependTo($label);\r
- break;\r
- \r
- case 'select':\r
- $input = $('<select name="context-menu-input-'+ key +'">').appendTo($label);\r
- if (item.options) {\r
- $.each(item.options, function(value, text) {\r
- $('<option></option>').val(value).text(text).appendTo($input);\r
- });\r
- $input.val(item.selected);\r
- }\r
- break;\r
- \r
- case 'sub':\r
- $('<span></span>').html(item._name || item.name).appendTo($t);\r
- item.appendTo = item.$node;\r
- op.create(item, root);\r
- $t.data('contextMenu', item).addClass('context-menu-submenu');\r
- item.callback = null;\r
- break;\r
- \r
- case 'html':\r
- $(item.html).appendTo($t);\r
- break;\r
- \r
- default:\r
- $.each([opt, root], function(i,k){\r
- k.commands[key] = item;\r
- if ($.isFunction(item.callback)) {\r
- k.callbacks[key] = item.callback;\r
- }\r
- });\r
- \r
- $('<span></span>').html(item._name || item.name || "").appendTo($t);\r
- break;\r
- }\r
- \r
- // disable key listener in <input>\r
- if (item.type && item.type != 'sub' && item.type != 'html') {\r
- $input\r
- .on('focus', handle.focusInput)\r
- .on('blur', handle.blurInput);\r
- \r
- if (item.events) {\r
- $input.on(item.events);\r
- }\r
- }\r
- \r
- // add icons\r
- if (item.icon) {\r
- $t.addClass("icon icon-" + item.icon);\r
- }\r
- }\r
- \r
- // cache contained elements\r
- item.$input = $input;\r
- item.$label = $label;\r
-\r
- // attach item to menu\r
- $t.appendTo(opt.$menu);\r
- \r
- // Disable text selection\r
- if (!opt.hasTypes) {\r
- if($.browser.msie) {\r
- $t.on('selectstart.disableTextSelect', handle.abortevent);\r
- } else if(!$.browser.mozilla) {\r
- $t.on('mousedown.disableTextSelect', handle.abortevent);\r
- }\r
- }\r
- });\r
- // attach contextMenu to <body> (to bypass any possible overflow:hidden issues on parents of the trigger element)\r
- if (!opt.$node) {\r
- opt.$menu.css('display', 'none').addClass('context-menu-root');\r
- }\r
- opt.$menu.appendTo(opt.appendTo || document.body);\r
- },\r
- update: function(opt, root) {\r
- var $this = this;\r
- if (root === undefined) {\r
- root = opt;\r
- // determine widths of submenus, as CSS won't grow them automatically\r
- // position:absolute > position:absolute; min-width:100; max-width:200; results in width: 100;\r
- // kinda sucks hard...\r
- opt.$menu.find('ul').andSelf().css({position: 'static', display: 'block'}).each(function(){\r
- var $this = $(this);\r
- $this.width($this.css('position', 'absolute').width())\r
- .css('position', 'static');\r
- }).css({position: '', display: ''});\r
- }\r
- // re-check disabled for each item\r
- opt.$menu.children().each(function(){\r
- var $item = $(this),\r
- key = $item.data('contextMenuKey'),\r
- item = opt.items[key],\r
- disabled = ($.isFunction(item.disabled) && item.disabled.call($this, key, root)) || item.disabled === true;\r
-\r
- // dis- / enable item\r
- $item[disabled ? 'addClass' : 'removeClass']('disabled');\r
- \r
- if (item.type) {\r
- // dis- / enable input elements\r
- $item.find('input, select, textarea').prop('disabled', disabled);\r
- \r
- // update input states\r
- switch (item.type) {\r
- case 'text':\r
- case 'textarea':\r
- item.$input.val(item.value || "");\r
- break;\r
- \r
- case 'checkbox':\r
- case 'radio':\r
- item.$input.val(item.value || "").prop('checked', !!item.selected);\r
- break;\r
- \r
- case 'select':\r
- item.$input.val(item.selected || "");\r
- break;\r
- }\r
- }\r
- \r
- if (item.$menu) {\r
- // update sub-menu\r
- op.update.call($this, item, root);\r
- }\r
- });\r
- },\r
- layer: function(opt, zIndex) {\r
- // add transparent layer for click area\r
- // filter and background for Internet Explorer, Issue #23\r
- return opt.$layer = $('<div id="context-menu-layer" style="position:fixed; z-index:' + zIndex + '; top:0; left:0; opacity: 0; filter: alpha(opacity=0); background-color: #000;"></div>')\r
- .css({height: $win.height(), width: $win.width(), display: 'block'})\r
- .data('contextMenuRoot', opt)\r
- .insertBefore(this)\r
- .on('contextmenu', handle.abortevent)\r
- .on('mousedown', handle.layerClick);\r
- }\r
- };\r
-\r
-// split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key\r
-function splitAccesskey(val) {\r
- var t = val.split(/\s+/),\r
- keys = [];\r
- \r
- for (var i=0, k; k = t[i]; i++) {\r
- k = k[0].toUpperCase(); // first character only\r
- // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it.\r
- // a map to look up already used access keys would be nice\r
- keys.push(k);\r
- }\r
- \r
- return keys;\r
-}\r
-\r
-// handle contextMenu triggers\r
-$.fn.contextMenu = function(operation) {\r
- if (operation === undefined) {\r
- this.first().trigger('contextmenu');\r
- } else if (operation.x && operation.y) {\r
- this.first().trigger(jQuery.Event("contextmenu", {pageX: operation.x, pageY: operation.y}));\r
- } else if (operation === "hide") {\r
- var $menu = this.data('contextMenu').$menu;\r
- $menu && $menu.trigger('contextmenu:hide');\r
- } else if (operation) {\r
- this.removeClass('context-menu-disabled');\r
- } else if (!operation) {\r
- this.addClass('context-menu-disabled');\r
- }\r
- \r
- return this;\r
-};\r
-\r
-// manage contextMenu instances\r
-$.contextMenu = function(operation, options) {\r
- if (typeof operation != 'string') {\r
- options = operation;\r
- operation = 'create';\r
- }\r
- \r
- if (typeof options == 'string') {\r
- options = {selector: options};\r
- } else if (options === undefined) {\r
- options = {};\r
- }\r
- \r
- // merge with default options\r
- var o = $.extend(true, {}, defaults, options || {}),\r
- $body = $body = $(document);\r
- \r
- switch (operation) {\r
- case 'create':\r
- // no selector no joy\r
- if (!o.selector) {\r
- throw new Error('No selector specified');\r
- }\r
- // make sure internal classes are not bound to\r
- if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) {\r
- throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className');\r
- }\r
- if (!o.build && (!o.items || $.isEmptyObject(o.items))) {\r
- throw new Error('No Items sepcified');\r
- }\r
- counter ++;\r
- o.ns = '.contextMenu' + counter;\r
- namespaces[o.selector] = o.ns;\r
- menus[o.ns] = o;\r
- \r
- if (!initialized) {\r
- // make sure item click is registered first\r
- $body\r
- .on({\r
- 'contextmenu:hide.contextMenu': handle.hideMenu,\r
- 'prevcommand.contextMenu': handle.prevItem,\r
- 'nextcommand.contextMenu': handle.nextItem,\r
- 'contextmenu.contextMenu': handle.abortevent,\r
- 'mouseenter.contextMenu': handle.menuMouseenter,\r
- 'mouseleave.contextMenu': handle.menuMouseleave\r
- }, '.context-menu-list')\r
- .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick)\r
- .on({\r
- 'mouseup.contextMenu': handle.itemClick,\r
- 'contextmenu:focus.contextMenu': handle.focusItem,\r
- 'contextmenu:blur.contextMenu': handle.blurItem,\r
- 'contextmenu.contextMenu': handle.abortevent,\r
- 'mouseenter.contextMenu': handle.itemMouseenter,\r
- 'mouseleave.contextMenu': handle.itemMouseleave\r
- }, '.context-menu-item');\r
-\r
- initialized = true;\r
- }\r
- \r
- // engage native contextmenu event\r
- $body\r
- .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu);\r
- \r
- switch (o.trigger) {\r
- case 'hover':\r
- $body\r
- .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter)\r
- .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave); \r
- break;\r
- \r
- case 'left':\r
- $body.on('click' + o.ns, o.selector, o, handle.click);\r
- break;\r
- /*\r
- default:\r
- // http://www.quirksmode.org/dom/events/contextmenu.html\r
- $body\r
- .on('mousedown' + o.ns, o.selector, o, handle.mousedown)\r
- .on('mouseup' + o.ns, o.selector, o, handle.mouseup);\r
- break;\r
- */\r
- }\r
- \r
- // create menu\r
- if (!o.build) {\r
- op.create(o);\r
- }\r
- break;\r
- \r
- case 'destroy':\r
- if (!o.selector) {\r
- $body.off('.contextMenu .contextMenuAutoHide');\r
- $.each(namespaces, function(key, value) {\r
- $body.off(value);\r
- });\r
- \r
- namespaces = {};\r
- menus = {};\r
- counter = 0;\r
- initialized = false;\r
- \r
- $('.context-menu-list').remove();\r
- } else if (namespaces[o.selector]) {\r
- try {\r
- if (menus[namespaces[o.selector]].$menu) {\r
- menus[namespaces[o.selector]].$menu.remove();\r
- }\r
- \r
- delete menus[namespaces[o.selector]];\r
- } catch(e) {\r
- menus[namespaces[o.selector]] = null;\r
- }\r
- \r
- $body.off(namespaces[o.selector]);\r
- }\r
- break;\r
- \r
- case 'html5':\r
- // if <command> or <menuitem> are not handled by the browser,\r
- // or options was a bool true,\r
- // initialize $.contextMenu for them\r
- if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options == "boolean" && options)) {\r
- $('menu[type="context"]').each(function() {\r
- if (this.id) {\r
- $.contextMenu({\r
- selector: '[contextmenu=' + this.id +']',\r
- items: $.contextMenu.fromMenu(this)\r
- });\r
- }\r
- }).css('display', 'none');\r
- }\r
- break;\r
- \r
- default:\r
- throw new Error('Unknown operation "' + operation + '"');\r
- }\r
- \r
- return this;\r
-};\r
-\r
-// import values into <input> commands\r
-$.contextMenu.setInputValues = function(opt, data) {\r
- if (data === undefined) {\r
- data = {};\r
- }\r
- \r
- $.each(opt.inputs, function(key, item) {\r
- switch (item.type) {\r
- case 'text':\r
- case 'textarea':\r
- item.value = data[key] || "";\r
- break;\r
-\r
- case 'checkbox':\r
- item.selected = data[key] ? true : false;\r
- break;\r
- \r
- case 'radio':\r
- item.selected = (data[item.radio] || "") == item.value ? true : false;\r
- break;\r
- \r
- case 'select':\r
- item.selected = data[key] || "";\r
- break;\r
- }\r
- });\r
-};\r
-\r
-// export values from <input> commands\r
-$.contextMenu.getInputValues = function(opt, data) {\r
- if (data === undefined) {\r
- data = {};\r
- }\r
- \r
- $.each(opt.inputs, function(key, item) {\r
- switch (item.type) {\r
- case 'text':\r
- case 'textarea':\r
- case 'select':\r
- data[key] = item.$input.val();\r
- break;\r
-\r
- case 'checkbox':\r
- data[key] = item.$input.prop('checked');\r
- break;\r
- \r
- case 'radio':\r
- if (item.$input.prop('checked')) {\r
- data[item.radio] = item.value;\r
- }\r
- break;\r
- }\r
- });\r
- \r
- return data;\r
-};\r
-\r
-// find <label for="xyz">\r
-function inputLabel(node) {\r
- return (node.id && $('label[for="'+ node.id +'"]').val()) || node.name;\r
-}\r
-\r
-// convert <menu> to items object\r
-function menuChildren(items, $children, counter) {\r
- if (!counter) {\r
- counter = 0;\r
- }\r
- \r
- $children.each(function() {\r
- var $node = $(this),\r
- node = this,\r
- nodeName = this.nodeName.toLowerCase(),\r
- label,\r
- item;\r
- \r
- // extract <label><input>\r
- if (nodeName == 'label' && $node.find('input, textarea, select').length) {\r
- label = $node.text();\r
- $node = $node.children().first();\r
- node = $node.get(0);\r
- nodeName = node.nodeName.toLowerCase();\r
- }\r
- \r
- /*\r
- * <menu> accepts flow-content as children. that means <embed>, <canvas> and such are valid menu items.\r
- * Not being the sadistic kind, $.contextMenu only accepts:\r
- * <command>, <menuitem>, <hr>, <span>, <p> <input [text, radio, checkbox]>, <textarea>, <select> and of course <menu>.\r
- * Everything else will be imported as an html node, which is not interfaced with contextMenu.\r
- */\r
- \r
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command\r
- switch (nodeName) {\r
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element\r
- case 'menu':\r
- item = {name: $node.attr('label'), items: {}};\r
- menuChildren(item.items, $node.children(), counter);\r
- break;\r
- \r
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command\r
- case 'a':\r
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command\r
- case 'button':\r
- item = {\r
- name: $node.text(),\r
- disabled: !!$node.attr('disabled'),\r
- callback: (function(){ return function(){ $node.click(); }; })()\r
- };\r
- break;\r
- \r
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command\r
-\r
- case 'menuitem':\r
- case 'command':\r
- switch ($node.attr('type')) {\r
- case undefined:\r
- case 'command':\r
- case 'menuitem':\r
- item = {\r
- name: $node.attr('label'),\r
- disabled: !!$node.attr('disabled'),\r
- callback: (function(){ return function(){ $node.click(); }; })()\r
- };\r
- break;\r
- \r
- case 'checkbox':\r
- item = {\r
- type: 'checkbox',\r
- disabled: !!$node.attr('disabled'),\r
- name: $node.attr('label'),\r
- selected: !!$node.attr('checked')\r
- };\r
- break;\r
- \r
- case 'radio':\r
- item = {\r
- type: 'radio',\r
- disabled: !!$node.attr('disabled'),\r
- name: $node.attr('label'),\r
- radio: $node.attr('radiogroup'),\r
- value: $node.attr('id'),\r
- selected: !!$node.attr('checked')\r
- };\r
- break;\r
- \r
- default:\r
- item = undefined;\r
- }\r
- break;\r
- \r
- case 'hr':\r
- item = '-------';\r
- break;\r
- \r
- case 'input':\r
- switch ($node.attr('type')) {\r
- case 'text':\r
- item = {\r
- type: 'text',\r
- name: label || inputLabel(node),\r
- disabled: !!$node.attr('disabled'),\r
- value: $node.val()\r
- };\r
- break;\r
- \r
- case 'checkbox':\r
- item = {\r
- type: 'checkbox',\r
- name: label || inputLabel(node),\r
- disabled: !!$node.attr('disabled'),\r
- selected: !!$node.attr('checked')\r
- };\r
- break;\r
- \r
- case 'radio':\r
- item = {\r
- type: 'radio',\r
- name: label || inputLabel(node),\r
- disabled: !!$node.attr('disabled'),\r
- radio: !!$node.attr('name'),\r
- value: $node.val(),\r
- selected: !!$node.attr('checked')\r
- };\r
- break;\r
- \r
- default:\r
- item = undefined;\r
- break;\r
- }\r
- break;\r
- \r
- case 'select':\r
- item = {\r
- type: 'select',\r
- name: label || inputLabel(node),\r
- disabled: !!$node.attr('disabled'),\r
- selected: $node.val(),\r
- options: {}\r
- };\r
- $node.children().each(function(){\r
- item.options[this.value] = $(this).text();\r
- });\r
- break;\r
- \r
- case 'textarea':\r
- item = {\r
- type: 'textarea',\r
- name: label || inputLabel(node),\r
- disabled: !!$node.attr('disabled'),\r
- value: $node.val()\r
- };\r
- break;\r
- \r
- case 'label':\r
- break;\r
- \r
- default:\r
- item = {type: 'html', html: $node.clone(true)};\r
- break;\r
- }\r
- \r
- if (item) {\r
- counter++;\r
- items['key' + counter] = item;\r
- }\r
- });\r
-}\r
-\r
-// convert html5 menu\r
-$.contextMenu.fromMenu = function(element) {\r
- var $this = $(element),\r
- items = {};\r
- \r
- menuChildren(items, $this.children());\r
- \r
- return items;\r
-};\r
-\r
-// make defaults accessible\r
-$.contextMenu.defaults = defaults;\r
-$.contextMenu.types = types;\r
-\r
-})(jQuery);\r
+++ /dev/null
-/*!
- * jQuery UI Position 1.8.13
- *
- * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Position
- */
-(function( $, undefined ) {
-
-$.ui = $.ui || {};
-
-var horizontalPositions = /left|center|right/,
- verticalPositions = /top|center|bottom/,
- center = "center",
- _position = $.fn.position,
- _offset = $.fn.offset;
-
-$.fn.position = function( options ) {
- if ( !options || !options.of ) {
- return _position.apply( this, arguments );
- }
-
- // make a copy, we don't want to modify arguments
- options = $.extend( {}, options );
-
- var target = $( options.of ),
- targetElem = target[0],
- collision = ( options.collision || "flip" ).split( " " ),
- offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ],
- targetWidth,
- targetHeight,
- basePosition;
-
- if ( targetElem.nodeType === 9 ) {
- targetWidth = target.width();
- targetHeight = target.height();
- basePosition = { top: 0, left: 0 };
- // TODO: use $.isWindow() in 1.9
- } else if ( targetElem.setTimeout ) {
- targetWidth = target.width();
- targetHeight = target.height();
- basePosition = { top: target.scrollTop(), left: target.scrollLeft() };
- } else if ( targetElem.preventDefault ) {
- // force left top to allow flipping
- options.at = "left top";
- targetWidth = targetHeight = 0;
- basePosition = { top: options.of.pageY, left: options.of.pageX };
- } else {
- targetWidth = target.outerWidth();
- targetHeight = target.outerHeight();
- basePosition = target.offset();
- }
-
- // force my and at to have valid horizontal and veritcal positions
- // if a value is missing or invalid, it will be converted to center
- $.each( [ "my", "at" ], function() {
- var pos = ( options[this] || "" ).split( " " );
- if ( pos.length === 1) {
- pos = horizontalPositions.test( pos[0] ) ?
- pos.concat( [center] ) :
- verticalPositions.test( pos[0] ) ?
- [ center ].concat( pos ) :
- [ center, center ];
- }
- pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : center;
- pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : center;
- options[ this ] = pos;
- });
-
- // normalize collision option
- if ( collision.length === 1 ) {
- collision[ 1 ] = collision[ 0 ];
- }
-
- // normalize offset option
- offset[ 0 ] = parseInt( offset[0], 10 ) || 0;
- if ( offset.length === 1 ) {
- offset[ 1 ] = offset[ 0 ];
- }
- offset[ 1 ] = parseInt( offset[1], 10 ) || 0;
-
- if ( options.at[0] === "right" ) {
- basePosition.left += targetWidth;
- } else if ( options.at[0] === center ) {
- basePosition.left += targetWidth / 2;
- }
-
- if ( options.at[1] === "bottom" ) {
- basePosition.top += targetHeight;
- } else if ( options.at[1] === center ) {
- basePosition.top += targetHeight / 2;
- }
-
- basePosition.left += offset[ 0 ];
- basePosition.top += offset[ 1 ];
-
- return this.each(function() {
- var elem = $( this ),
- elemWidth = elem.outerWidth(),
- elemHeight = elem.outerHeight(),
- marginLeft = parseInt( $.curCSS( this, "marginLeft", true ) ) || 0,
- marginTop = parseInt( $.curCSS( this, "marginTop", true ) ) || 0,
- collisionWidth = elemWidth + marginLeft +
- ( parseInt( $.curCSS( this, "marginRight", true ) ) || 0 ),
- collisionHeight = elemHeight + marginTop +
- ( parseInt( $.curCSS( this, "marginBottom", true ) ) || 0 ),
- position = $.extend( {}, basePosition ),
- collisionPosition;
-
- if ( options.my[0] === "right" ) {
- position.left -= elemWidth;
- } else if ( options.my[0] === center ) {
- position.left -= elemWidth / 2;
- }
-
- if ( options.my[1] === "bottom" ) {
- position.top -= elemHeight;
- } else if ( options.my[1] === center ) {
- position.top -= elemHeight / 2;
- }
-
- // prevent fractions (see #5280)
- position.left = Math.round( position.left );
- position.top = Math.round( position.top );
-
- collisionPosition = {
- left: position.left - marginLeft,
- top: position.top - marginTop
- };
-
- $.each( [ "left", "top" ], function( i, dir ) {
- if ( $.ui.position[ collision[i] ] ) {
- $.ui.position[ collision[i] ][ dir ]( position, {
- targetWidth: targetWidth,
- targetHeight: targetHeight,
- elemWidth: elemWidth,
- elemHeight: elemHeight,
- collisionPosition: collisionPosition,
- collisionWidth: collisionWidth,
- collisionHeight: collisionHeight,
- offset: offset,
- my: options.my,
- at: options.at
- });
- }
- });
-
- if ( $.fn.bgiframe ) {
- elem.bgiframe();
- }
- elem.offset( $.extend( position, { using: options.using } ) );
- });
-};
-
-$.ui.position = {
- fit: {
- left: function( position, data ) {
- var win = $( window ),
- over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft();
- position.left = over > 0 ? position.left - over : Math.max( position.left - data.collisionPosition.left, position.left );
- },
- top: function( position, data ) {
- var win = $( window ),
- over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop();
- position.top = over > 0 ? position.top - over : Math.max( position.top - data.collisionPosition.top, position.top );
- }
- },
-
- flip: {
- left: function( position, data ) {
- if ( data.at[0] === center ) {
- return;
- }
- var win = $( window ),
- over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(),
- myOffset = data.my[ 0 ] === "left" ?
- -data.elemWidth :
- data.my[ 0 ] === "right" ?
- data.elemWidth :
- 0,
- atOffset = data.at[ 0 ] === "left" ?
- data.targetWidth :
- -data.targetWidth,
- offset = -2 * data.offset[ 0 ];
- position.left += data.collisionPosition.left < 0 ?
- myOffset + atOffset + offset :
- over > 0 ?
- myOffset + atOffset + offset :
- 0;
- },
- top: function( position, data ) {
- if ( data.at[1] === center ) {
- return;
- }
- var win = $( window ),
- over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(),
- myOffset = data.my[ 1 ] === "top" ?
- -data.elemHeight :
- data.my[ 1 ] === "bottom" ?
- data.elemHeight :
- 0,
- atOffset = data.at[ 1 ] === "top" ?
- data.targetHeight :
- -data.targetHeight,
- offset = -2 * data.offset[ 1 ];
- position.top += data.collisionPosition.top < 0 ?
- myOffset + atOffset + offset :
- over > 0 ?
- myOffset + atOffset + offset :
- 0;
- }
- }
-};
-
-// offset setter from jQuery 1.4
-if ( !$.offset.setOffset ) {
- $.offset.setOffset = function( elem, options ) {
- // set position first, in-case top/left are set even on static elem
- if ( /static/.test( $.curCSS( elem, "position" ) ) ) {
- elem.style.position = "relative";
- }
- var curElem = $( elem ),
- curOffset = curElem.offset(),
- curTop = parseInt( $.curCSS( elem, "top", true ), 10 ) || 0,
- curLeft = parseInt( $.curCSS( elem, "left", true ), 10) || 0,
- props = {
- top: (options.top - curOffset.top) + curTop,
- left: (options.left - curOffset.left) + curLeft
- };
-
- if ( 'using' in options ) {
- options.using.call( elem, props );
- } else {
- curElem.css( props );
- }
- };
-
- $.fn.offset = function( options ) {
- var elem = this[ 0 ];
- if ( !elem || !elem.ownerDocument ) { return null; }
- if ( options ) {
- return this.each(function() {
- $.offset.setOffset( this, options );
- });
- }
- return _offset.call( this );
- };
-}
-
-}( jQuery ));
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\r
-\r
-<html xmlns="http://www.w3.org/1999/xhtml">\r
-\r
- <head>\r
- <title>jQuery Context Menu Plugin Demo</title>\r
- <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />\r
- \r
- <style type="text/css">\r
- BODY,\r
- HTML {\r
- padding: 0px;\r
- margin: 0px;\r
- }\r
- BODY {\r
- font-family: Verdana, Arial, Helvetica, sans-serif;\r
- font-size: 11px;\r
- background: #FFF;\r
- padding: 15px;\r
- }\r
- \r
- H1 {\r
- font-family: Georgia, serif;\r
- font-size: 20px;\r
- font-weight: normal;\r
- }\r
- \r
- H2 {\r
- font-family: Georgia, serif;\r
- font-size: 16px;\r
- font-weight: normal;\r
- margin: 0px 0px 10px 0px;\r
- }\r
- \r
- #myDiv {\r
- width: 150px;\r
- border: solid 1px #2AA7DE;\r
- background: #6CC8EF;\r
- text-align: center;\r
- padding: 4em .5em;\r
- margin: 1em;\r
- float: left;\r
- }\r
- \r
- #myList {\r
- margin: 1em;\r
- float: left;\r
- }\r
- \r
- #myList UL {\r
- padding: 0px;\r
- margin: 0em 1em;\r
- }\r
- \r
- #myList LI {\r
- width: 100px;\r
- border: solid 1px #2AA7DE;\r
- background: #6CC8EF;\r
- padding: 5px 5px;\r
- margin: 2px 0px;\r
- list-style: none;\r
- }\r
- \r
- #options {\r
- clear: left;\r
- }\r
- \r
- #options INPUT {\r
- font-family: Verdana, Arial, Helvetica, sans-serif;\r
- font-size: 11px;\r
- width: 150px;\r
- }\r
- \r
- </style> \r
- \r
- <script src="../js/lib/jquery-1.4.2.min.js" type="text/javascript"></script>\r
- <script src="jquery.contextMenu.js" type="text/javascript"></script>\r
- <link href="jquery.contextMenu.css" rel="stylesheet" type="text/css" />\r
- \r
- <script type="text/javascript">\r
- \r
- $(document).ready( function() {\r
- \r
- // Show menu when #myDiv is clicked\r
- $("#myDiv").contextMenu({\r
- menu: 'myMenu'\r
- },\r
- function(action, el, pos) {\r
- alert(\r
- 'Action: ' + action + '\n\n' +\r
- 'Element ID: ' + $(el).attr('id') + '\n\n' + \r
- 'X: ' + pos.x + ' Y: ' + pos.y + ' (relative to element)\n\n' + \r
- 'X: ' + pos.docX + ' Y: ' + pos.docY+ ' (relative to document)'\r
- );\r
- });\r
- \r
- // Show menu when a list item is clicked\r
- $("#myList UL LI").contextMenu({\r
- menu: 'myMenu'\r
- }, function(action, el, pos) {\r
- alert(\r
- 'Action: ' + action + '\n\n' +\r
- 'Element text: ' + $(el).text() + '\n\n' + \r
- 'X: ' + pos.x + ' Y: ' + pos.y + ' (relative to element)\n\n' + \r
- 'X: ' + pos.docX + ' Y: ' + pos.docY+ ' (relative to document)'\r
- );\r
- });\r
- \r
- // Disable menus\r
- $("#disableMenus").click( function() {\r
- $('#myDiv, #myList UL LI').disableContextMenu();\r
- $(this).attr('disabled', true);\r
- $("#enableMenus").attr('disabled', false);\r
- });\r
- \r
- // Enable menus\r
- $("#enableMenus").click( function() {\r
- $('#myDiv, #myList UL LI').enableContextMenu();\r
- $(this).attr('disabled', true);\r
- $("#disableMenus").attr('disabled', false);\r
- });\r
- \r
- // Disable cut/copy\r
- $("#disableItems").click( function() {\r
- $('#myMenu').disableContextMenuItems('#cut,#copy');\r
- $(this).attr('disabled', true);\r
- $("#enableItems").attr('disabled', false);\r
- });\r
- \r
- // Enable cut/copy\r
- $("#enableItems").click( function() {\r
- $('#myMenu').enableContextMenuItems('#cut,#copy');\r
- $(this).attr('disabled', true);\r
- $("#disableItems").attr('disabled', false);\r
- }); \r
- \r
- });\r
- \r
- </script>\r
- </head>\r
- \r
- <body>\r
- \r
- <h1>jQuery Context Menu Plugin Demo</h1>\r
- <p>\r
- This plugin lets you add context menu functionality to your web applications.\r
- </p>\r
- \r
- <p>\r
- <strong>Tip:</strong> Try using your keyboard to make a selection.\r
- </p>\r
- \r
- <p>\r
- <a href="http://abeautifulsite.net/2008/09/jquery-context-menu-plugin/">Back to the project page</a>\r
- </p>\r
- \r
- <h2>Demo</h2>\r
- \r
- <div id="myDiv">\r
- Right click to view the context menu\r
- </div>\r
- \r
- <div id="myList">\r
- <ul>\r
- <li>Item 1</li>\r
- <li>Item 2</li>\r
- <li>Item 3</li>\r
- <li>Item 4</li>\r
- <li>Item 5</li>\r
- <li>Item 6</li>\r
- </ul>\r
- </div>\r
- \r
- <div id="options">\r
- <p>\r
- <input type="button" id="disableItems" value="Disable Cut/Copy" />\r
- <input type="button" id="enableItems" value="Enable Cut/Copy" disabled="disabled" />\r
- </p>\r
- \r
- <p>\r
- <input type="button" id="disableMenus" value="Disable Context Menus" />\r
- <input type="button" id="enableMenus" value="Enable Context Menus" disabled="disabled" />\r
- </p>\r
- </div>\r
- \r
- <ul id="myMenu" class="contextMenu">\r
- <li class="edit"><a href="#edit">Edit</a></li>\r
- <li class="cut separator"><a href="#cut">Cut</a>\r
- <ul>\r
- <li class="pe"><a href="#pe">Pe</a></li>\r
- </ul>\r
- </li>\r
- <li class="copy"><a href="#copy">Copy</a></li>\r
- <li class="paste"><a href="#paste">Paste</a></li>\r
- <li class="delete"><a href="#delete">Delete</a></li>\r
- <li class="quit separator"><a href="#quit">Quit</a></li>\r
- </ul>\r
- \r
- </body>\r
-</html>\r
+++ /dev/null
-/* Generic context menu styles */\r
-.contextMenu {\r
- position: absolute;\r
- width: 120px;\r
- z-index: 99999;\r
- border: solid 1px #CCC;\r
- background: #EEE;\r
- padding: 0px;\r
- margin: 0px;\r
- display: none;\r
-}\r
-\r
-.contextMenu LI {\r
- list-style: none;\r
- padding: 0px;\r
- margin: 0px;\r
-}\r
-\r
-.contextMenu A {\r
- color: #333;\r
- text-decoration: none;\r
- display: block;\r
- line-height: 20px;\r
- height: 20px;\r
- background-position: 6px center;\r
- background-repeat: no-repeat;\r
- outline: none;\r
- padding: 1px 5px;\r
- padding-left: 28px;\r
-}\r
-\r
-.contextMenu LI.hover A {\r
- color: #FFF;\r
- background-color: #3399FF;\r
-}\r
-\r
-.contextMenu LI.disabled A {\r
- color: #AAA;\r
- cursor: default;\r
-}\r
-\r
-.contextMenu LI.hover.disabled A {\r
- background-color: transparent;\r
-}\r
-\r
-.contextMenu LI.separator {\r
- border-top: solid 1px #CCC;\r
-}\r
-\r
-/*\r
- Adding Icons\r
- \r
- You can add icons to the context menu by adding\r
- classes to the respective LI element(s)\r
-*/\r
-\r
-.contextMenu LI.edit A { background-image: url(images/page_white_edit.png); }\r
-.contextMenu LI.cut A { background-image: url(images/cut.png); }\r
-.contextMenu LI.copy A { background-image: url(images/page_white_copy.png); }\r
-.contextMenu LI.paste A { background-image: url(images/page_white_paste.png); }\r
-.contextMenu LI.delete A { background-image: url(images/page_white_delete.png); }\r
-.contextMenu LI.quit A { background-image: url(images/door.png); }\r
+++ /dev/null
-// jQuery Context Menu Plugin\r
-//\r
-// Version 1.01\r
-//\r
-// Cory S.N. LaViska\r
-// A Beautiful Site (http://abeautifulsite.net/)\r
-//\r
-// More info: http://abeautifulsite.net/2008/09/jquery-context-menu-plugin/\r
-//\r
-// Terms of Use\r
-//\r
-// This plugin is dual-licensed under the GNU General Public License\r
-// and the MIT License and is copyright A Beautiful Site, LLC.\r
-//\r
-if(jQuery)( function() {\r
- $.extend($.fn, {\r
- \r
- contextMenu: function(o, callback) {\r
- // Defaults\r
- if( o.menu == undefined ) return false;\r
- if( o.inSpeed == undefined ) o.inSpeed = 150;\r
- if( o.outSpeed == undefined ) o.outSpeed = 75;\r
- // 0 needs to be -1 for expected results (no fade)\r
- if( o.inSpeed == 0 ) o.inSpeed = -1;\r
- if( o.outSpeed == 0 ) o.outSpeed = -1;\r
- // Loop each context menu\r
- $(this).each( function() {\r
- var el = $(this);\r
- var offset = $(el).offset();\r
- // Add contextMenu class\r
- $('#' + o.menu).addClass('contextMenu');\r
- // Simulate a true right click\r
- $(this).mousedown( function(e) {\r
- var evt = e;\r
- evt.stopPropagation();\r
- $(this).mouseup( function(e) {\r
- e.stopPropagation();\r
- var srcElement = $(this);\r
- $(this).unbind('mouseup');\r
- if( evt.button == 2 ) {\r
- // Hide context menus that may be showing\r
- $(".contextMenu").hide();\r
- // Get this context menu\r
- var menu = $('#' + o.menu);\r
- \r
- if( $(el).hasClass('disabled') ) return false;\r
- \r
- // Detect mouse position\r
- var d = {}, x, y;\r
- if( self.innerHeight ) {\r
- d.pageYOffset = self.pageYOffset;\r
- d.pageXOffset = self.pageXOffset;\r
- d.innerHeight = self.innerHeight;\r
- d.innerWidth = self.innerWidth;\r
- } else if( document.documentElement &&\r
- document.documentElement.clientHeight ) {\r
- d.pageYOffset = document.documentElement.scrollTop;\r
- d.pageXOffset = document.documentElement.scrollLeft;\r
- d.innerHeight = document.documentElement.clientHeight;\r
- d.innerWidth = document.documentElement.clientWidth;\r
- } else if( document.body ) {\r
- d.pageYOffset = document.body.scrollTop;\r
- d.pageXOffset = document.body.scrollLeft;\r
- d.innerHeight = document.body.clientHeight;\r
- d.innerWidth = document.body.clientWidth;\r
- }\r
- (e.pageX) ? x = e.pageX : x = e.clientX + d.scrollLeft;\r
- (e.pageY) ? y = e.pageY : y = e.clientY + d.scrollTop;\r
- \r
- // Show the menu\r
- $(document).unbind('click');\r
- $(menu).css({ top: y, left: x }).fadeIn(o.inSpeed);\r
- // Hover events\r
- $(menu).find('A').mouseover( function() {\r
- $(menu).find('LI.hover').removeClass('hover');\r
- $(this).parent().addClass('hover');\r
- }).mouseout( function() {\r
- $(menu).find('LI.hover').removeClass('hover');\r
- });\r
- \r
- // Keyboard\r
- $(document).keypress( function(e) {\r
- switch( e.keyCode ) {\r
- case 38: // up\r
- if( $(menu).find('LI.hover').size() == 0 ) {\r
- $(menu).find('LI:last').addClass('hover');\r
- } else {\r
- $(menu).find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover');\r
- if( $(menu).find('LI.hover').size() == 0 ) $(menu).find('LI:last').addClass('hover');\r
- }\r
- break;\r
- case 40: // down\r
- if( $(menu).find('LI.hover').size() == 0 ) {\r
- $(menu).find('LI:first').addClass('hover');\r
- } else {\r
- $(menu).find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover');\r
- if( $(menu).find('LI.hover').size() == 0 ) $(menu).find('LI:first').addClass('hover');\r
- }\r
- break;\r
- case 13: // enter\r
- $(menu).find('LI.hover A').trigger('click');\r
- break;\r
- case 27: // esc\r
- $(document).trigger('click');\r
- break\r
- }\r
- });\r
- \r
- // When items are selected\r
- $('#' + o.menu).find('A').unbind('click');\r
- $('#' + o.menu).find('LI:not(.disabled) A').click( function() {\r
- $(document).unbind('click').unbind('keypress');\r
- $(".contextMenu").hide();\r
- // Callback\r
- if( callback ) callback( $(this).attr('href').substr(1), $(srcElement), {x: x - offset.left, y: y - offset.top, docX: x, docY: y} );\r
- return false;\r
- });\r
- \r
- // Hide bindings\r
- setTimeout( function() { // Delay for Mozilla\r
- $(document).click( function() {\r
- $(document).unbind('click').unbind('keypress');\r
- $(menu).fadeOut(o.outSpeed);\r
- return false;\r
- });\r
- }, 0);\r
- }\r
- });\r
- });\r
- \r
- // Disable text selection\r
- if( $.browser.mozilla ) {\r
- $('#' + o.menu).each( function() { $(this).css({ 'MozUserSelect' : 'none' }); });\r
- } else if( $.browser.msie ) {\r
- $('#' + o.menu).each( function() { $(this).bind('selectstart.disableTextSelect', function() { return false; }); });\r
- } else {\r
- $('#' + o.menu).each(function() { $(this).bind('mousedown.disableTextSelect', function() { return false; }); });\r
- }\r
- // Disable browser context menu (requires both selectors to work in IE/Safari + FF/Chrome)\r
- $(el).add($('UL.contextMenu')).bind('contextmenu', function() { return false; });\r
- \r
- });\r
- return $(this);\r
- },\r
- \r
- // Disable context menu items on the fly\r
- disableContextMenuItems: function(o) {\r
- if( o == undefined ) {\r
- // Disable all\r
- $(this).find('LI').addClass('disabled');\r
- return( $(this) );\r
- }\r
- $(this).each( function() {\r
- if( o != undefined ) {\r
- var d = o.split(',');\r
- for( var i = 0; i < d.length; i++ ) {\r
- $(this).find('A[href="' + d[i] + '"]').parent().addClass('disabled');\r
- \r
- }\r
- }\r
- });\r
- return( $(this) );\r
- },\r
- \r
- // Enable context menu items on the fly\r
- enableContextMenuItems: function(o) {\r
- if( o == undefined ) {\r
- // Enable all\r
- $(this).find('LI.disabled').removeClass('disabled');\r
- return( $(this) );\r
- }\r
- $(this).each( function() {\r
- if( o != undefined ) {\r
- var d = o.split(',');\r
- for( var i = 0; i < d.length; i++ ) {\r
- $(this).find('A[href="' + d[i] + '"]').parent().removeClass('disabled');\r
- \r
- }\r
- }\r
- });\r
- return( $(this) );\r
- },\r
- \r
- // Disable context menu(s)\r
- disableContextMenu: function() {\r
- $(this).each( function() {\r
- $(this).addClass('disabled');\r
- });\r
- return( $(this) );\r
- },\r
- \r
- // Enable context menu(s)\r
- enableContextMenu: function() {\r
- $(this).each( function() {\r
- $(this).removeClass('disabled');\r
- });\r
- return( $(this) );\r
- },\r
- \r
- // Destroy context menu(s)\r
- destroyContextMenu: function() {\r
- // Destroy specified context menus\r
- $(this).each( function() {\r
- // Disable action\r
- $(this).unbind('mousedown').unbind('mouseup');\r
- });\r
- return( $(this) );\r
- }\r
- \r
- });\r
-})(jQuery);
\ No newline at end of file
#sidebar {
- position: absolute;
- top: 0px;
- right: 0px;
- bottom: 0px;
- width: 0px;
background-color: #FFF;
}
background-color: #C1C1C1;
}
-#side-search p {
- padding: .5em;
-}
#side-annotations {
- display: none;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ position: relative;
+}
+
+#side-annotations #annotations-container {
+ flex: 1;
+ position: relative;
+ overflow: hidden;
}
.annotations-list {
- position: absolute;
- left: 0;
- top: 28px;
- right: 0;
- bottom: 0;
+ flex: 1;
+ height: 100%;
overflow: auto;
padding: 1em 1em 0 1em;
}
-#side-annotations .active {
- font-weight: bold;
+#side-annotations .error-message {
+ padding: 1em;
}
#side-annotations .spinner {
position: absolute;
left: 0;
- top: 28px;
+ top: 0;
right: 0;
bottom: 0;
- text-align:center;
- padding-top: 50%;
background: white;
-}
-
-#side-annotations .spinner img {
- margin-top: -15px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
#side-annotations .akap {
/* = Gallery = */
/* =========== */
-#side-gallery .error_message
-{
- background-color: white;
- color: black;
- padding: 0.5em 1em;
+#side-gallery {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
}
+
.change-gallery-panel {
width: 100%;
border-bottom: 1px solid #777;
}
.gallery-image {
- position: absolute;
- top: 30px;
- right: 0;
- bottom: 0;
- left: 0;
- margin: 0;
- border: none;
- background-color: transparent;
+ flex: 1;
overflow: hidden;
+ position: relative;
}
.gallery-image img {
+#history-view-editor {
+ display: flex;
+ flex-direction: column;
+}
+
#history-view {
+ flex: 1;
overflow: auto;
- position: absolute;
- top: 30px; /* 0 for no toolbar, 30 for toolbar */
- bottom: 0px;
- left: 0px;
- right: 0px;
- z-index: 1;
}
/*
}
.edit-button, .delete-button, .accept-button, .tytul-button, .wyroznienie-button, .slowo-button, .znak-button {
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
+ font-size: 16px;
+ line-height: 1.5;
position: absolute;
- top: -21px;
+ top: -28px;
left: -1px;
width: 72px;
height: 28px;
height: 100%;
z-index: 0;
font-size: 10pt;
+ font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
+ line-height: 1.25;
/* background-color: ivory;*/
}
/* specialChars */
#specialCharsContainer {
text-align: center;
- width: 600px;
+ width: 640px;
height: 400px;
padding:20px;
background-color: gray;
position: absolute;
top: 20px;
- left: 20px;
- z-index:1000;
+ right: 20px;
+ z-index:10000;
overflow:auto;
}
#specialCharsContainer a {
-a, a:visited, a:active {
- color: blue;
- text-decoration: none;
-}
-
-a:hover {
- text-decoration: underline;
+html {
+ width: 100%;
+ height: 100%;
}
-
body {
- margin: 0;
- overflow: hidden;
- padding: 0;
- min-width: 960px;
- width: 100%;
height: 100%;
+ display: flex;
+ flex-direction: column;
}
-.vsplitbar {
- position: absolute;
- top: 0px;
- bottom: 0;
- right: 0;
- width: 26px;
- /*background: #C1C1C1 url(../img/gallery.png) no-repeat scroll center center;*/
- border-left: 2px solid #999;
- border-right: 2px solid #999;
- cursor: pointer;
- background: #C1C1C1;
- z-index:100;
- cursor: col-resize;
-}
-
-.vsplitbar:hover {
- background-color: #E6E6E6;
-}
-
-.vsplitbar p {
- font: 12px Helvetica,Verdana,sans-serif;
-
- -moz-transform: rotate(270deg);
- -moz-transform-origin: 50% 50%;
- -webkit-transform: rotate(270deg);
- -webkit-transform-origin: 50% 50%;
- -o-transform: rotate(-270deg);
- -o-transform-origin: bottom left;
- filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
- margin: 250px auto;
+#editor {
+ flex: 1;
}
#drag-layer {
}
.editor {
- position: absolute;
- top: 0px;
- bottom: 0;
- left: 0;
- right: 30px;
- overflow: hidden;
+ height: 100%;
}
-.sideless .editor {
- right: 0;
-}
.image-object {
padding-left: 1em;
font: 12px Sans, Helvetica, Verdana, sans-serif;
#html-view {
overflow: auto;
- position: absolute;
- top: 33px;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 1;
}
-#splitter {
- position: fixed;
- top: 31px;
- left: 0px;
- right: 0px;
- bottom: 0px;
- overflow: hidden;
-}
-
-
-#header {
- position: fixed;
- top: 0px;
- right: 0px;
- left: 0px;
- height: 30px;
- border-bottom: 1px solid #999;
-
- margin: 0;
- padding: 0;
- background-color: #C1C1C1;
- background-image: -webkit-gradient(linear, left top, left bottom, from(#C1C1C1), color-stop(0.9, #A2A2A2));
-
- /* Firefox 3.6 */
- background-image: -moz-linear-gradient(top left, #C1C1C1, #A2A2A2, 90%);
-
- font: 11px Helvetica, Verdana, sans-serif;
- font-weight: bold;
-
- z-index: 100;
-}
-
-
#header.saving {
background-color: #E1C1C1;
}
}
-
-#header h1, #header h1 a {
- margin: 0;
- padding: 0;
- font: 9px Helvetica, Verdana, sans-serif;
- font-weight: bold;
- float: left;
- padding: 3px 3px 2px 3px;
- color: #222;
- #line-height: 20px;
-}
-
-#header h1 a {
- text-decoration: none;
- color: #222;
-}
-
-#header img {
- border: 0;
-}
-
-.tabs {
- overflow: hidden;
- margin: 0;
- padding: 0;
- height: 31px;
- border: 0px;
- padding-left: 1em;
- float: left;
-}
-
-.tabs a {
- color: black;
-}
-
-#tabs-right {
- float: right;
- padding-right: 1em;
-}
-
-.tabs li {
- margin-top: 6px;
- margin-bottom: 0px;
-
- -webkit-user-select: none;
- cursor: pointer;
- display: block;
- float: left;
-
- font-weight: bold;
- color: #222;
- margin-left: 4px;
-
- background-color: #A2A2A2;
-
- -moz-box-shadow: 1px -1px 2px rgba(127, 127, 127, 0.25);
- -webkit-box-shadow: 1px -1px 2px rgba(127, 127, 127, 0.25);
-
- border: 1px solid #999;
- border-bottom-width: 0px;
- -moz-border-radius: 4px 4px 0px 0px;
- -webkit-border-radius: 4px;
- -webkit-border-bottom-left-radius: 0px;
- -webkit-border-bottom-right-radius: 0px;
-}
-
-#tabs li {
- height: 18px;
- padding-left: 12px;
- padding-right: 12px;
- padding-top: 5px;
-}
-
-.tabs li.active {
- background-color: #C1C1C1;
-}
-
-
-#tabs-right li {
- height: 20px;
- padding-left: 12px;
- padding-right: 12px;
- padding-top: 3px;
-}
-
-
-
-#tools {
- float: right;
- clear: right;
- overflow: hidden;
- margin: 0;
- padding: 0;
- height: 30px;
- margin-right: 5px;
- line-height: 30px;
- font-size: 10px;
- vertical-align: middle;
-}
-
-/* Remove extra padding in Firefox */
-button::-moz-focus-inner {
- border: 0;
- padding: 0;
-}
-
p { margin: 0;}
-#body-wrap {
- height: 100%;
- width: 100%;
-}
-
-#content {
- height: 100%;
- width: 100%;
-}
-
#loading-overlay {
background-color: #FFF;
position: absolute;
margin-left: -60px;
top: 50%;
margin-top: -10px;
-/* text-align: center;*/
}
#loading-message img {
* CodeMirror
*/
+#source-editor {
+ display: flex;
+ flex-direction: column;
+}
+#source-editor .CodeMirror {
+ flex: 1;
+}
+
.CodeMirror-line-numbers {
padding: 0px;
padding-top: 5px;
* HTML Editor view
*/
+#simple-editor {
+ display: flex;
+ flex-direction: column;
+}
+
.htmlview {
- z-index: 1;
- overflow: hidden;
+ flex: 1;
}
.htmlview .active[x-editable] {
}
+#objects-editor, #motifs-editor {
+ display: flex;
+ flex-direction: column;
+}
.scrolled {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
+ flex: 1;
overflow: auto;
}
* Toolbars
*/
-.toolbar {
- border-bottom: 1px solid #777;
- background-color: #C1C1C1;
- margin: 0px;
- padding: 0px;
- z-index: 100;
- height: 28px;
- position: relative;
-}
-
-.toolbar > .group_selector {
- position: absolute;
-
- margin: 1px 5px 1px 0;
- border: 1px solid #999;
- padding: 1px;
-
- top: 0px;
- left: 0px;
- bottom: 0px;
- width: 180px;
-}
-
-.toolbar > .group_selector + button {
- position: absolute;
- width: 20px;
- top: 0px;
- left: 180px;
- bottom: 0px;
-}
-
.toolbar > .button_group_container {
- position: absolute;
- top: 0px;
- left: 200px;
- right: 20px;
- bottom: 0px;
-
- overflow: hidden;
+ overflow: hidden;
}
-.toolbar > .button_group_container + button {
- position: absolute;
- width: 20px;
- top: 0px;
- right: 0px;
- bottom: 0px;
+.toolbar .button_group {
+ line-height: 2;
}
-
-.toolbar ul.button_group {
- margin: 0;
- padding: 0;
- width: 10000%;
-}
-
-.toolbar ul.button_group li {
- display: inline-block;
- margin: 0;
- padding: 0;
-}
-
-
-.toolbar .button_group button {
- display: block;
- margin: 4px 0 2px 0;
- padding: 2px 5px;
- border: none;
- background: none;
- color: #424242;
-}
-
-.toolbar .button_group button img {
- margin: 0;
- padding: 0;
- margin-bottom: -3px;
-}
-
-.toolbar .button_group button:hover,
-.toolbar .button_group button:active {
- background: #777;
- color: #FFF;
- -webkit-border-radius: 10px;
- -moz-border-radius: 10px;
-}
\ No newline at end of file
ScriptletCenter.prototype.XMLEditorSelectedText = function(editor) {
- return editor.selection();
+ return editor.getSelection();
};
ScriptletCenter.prototype.XMLEditorReplaceSelectedText = function(editor, replacement)
+++ /dev/null
-/* CodeMirror main module
- *
- * Implements the CodeMirror constructor and prototype, which take care
- * of initializing the editor frame, and providing the outside interface.
- */
-
-// The CodeMirrorConfig object is used to specify a default
-// configuration. If you specify such an object before loading this
-// file, the values you put into it will override the defaults given
-// below. You can also assign to it after loading.
-var CodeMirrorConfig = window.CodeMirrorConfig || {};
-
-var CodeMirror = (function(){
- function setDefaults(object, defaults) {
- for (var option in defaults) {
- if (!object.hasOwnProperty(option))
- object[option] = defaults[option];
- }
- }
- function forEach(array, action) {
- for (var i = 0; i < array.length; i++)
- action(array[i]);
- }
-
- // These default options can be overridden by passing a set of
- // options to a specific CodeMirror constructor. See manual.html for
- // their meaning.
- setDefaults(CodeMirrorConfig, {
- stylesheet: [],
- path: "",
- parserfile: [],
- basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"],
- iframeClass: null,
- passDelay: 200,
- passTime: 50,
- lineNumberDelay: 200,
- lineNumberTime: 50,
- continuousScanning: false,
- saveFunction: null,
- onChange: null,
- undoDepth: 50,
- undoDelay: 800,
- disableSpellcheck: true,
- textWrapping: true,
- readOnly: false,
- width: "",
- height: "300px",
- minHeight: 100,
- autoMatchParens: false,
- parserConfig: null,
- tabMode: "indent", // or "spaces", "default", "shift"
- reindentOnLoad: false,
- activeTokens: null,
- cursorActivity: null,
- lineNumbers: false,
- indentUnit: 2,
- domain: null
- });
-
- function addLineNumberDiv(container) {
- var nums = document.createElement("DIV"),
- scroller = document.createElement("DIV");
- nums.style.position = "absolute";
- nums.style.height = "100%";
- if (nums.style.setExpression) {
- try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");}
- catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions
- }
- nums.style.top = "0px";
- nums.style.left = "0px";
- nums.style.overflow = "hidden";
- container.appendChild(nums);
- scroller.className = "CodeMirror-line-numbers";
- nums.appendChild(scroller);
- scroller.innerHTML = "<div>1</div>";
- return nums;
- }
-
- function frameHTML(options) {
- if (typeof options.parserfile == "string")
- options.parserfile = [options.parserfile];
- if (typeof options.stylesheet == "string")
- options.stylesheet = [options.stylesheet];
-
- var html = ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><head>"];
- // Hack to work around a bunch of IE8-specific problems.
- html.push("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=EmulateIE7\"/>");
- forEach(options.stylesheet, function(file) {
- html.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + file + "\"/>");
- });
- forEach(options.basefiles.concat(options.parserfile), function(file) {
- if (!/^https?:/.test(file)) file = options.path + file;
- html.push("<script type=\"text/javascript\" src=\"" + file + "\"><" + "/script>");
- });
- html.push("</head><body style=\"border-width: 0;\" class=\"editbox\" spellcheck=\"" +
- (options.disableSpellcheck ? "false" : "true") + "\"></body></html>");
- return html.join("");
- }
-
- var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
-
- function CodeMirror(place, options) {
- // Use passed options, if any, to override defaults.
- this.options = options = options || {};
- setDefaults(options, CodeMirrorConfig);
-
- // Backward compatibility for deprecated options.
- if (options.dumbTabs) options.tabMode = "spaces";
- else if (options.normalTab) options.tabMode = "default";
-
- var frame = this.frame = document.createElement("IFRAME");
- if (options.iframeClass) frame.className = options.iframeClass;
- frame.frameBorder = 0;
- frame.style.border = "0";
- frame.style.width = '100%';
- frame.style.height = '100%';
- // display: block occasionally suppresses some Firefox bugs, so we
- // always add it, redundant as it sounds.
- frame.style.display = "block";
-
- var div = this.wrapping = document.createElement("DIV");
- div.style.position = "relative";
- div.className = "CodeMirror-wrapping";
- div.style.width = options.width;
- div.style.height = (options.height == "dynamic") ? options.minHeight + "px" : options.height;
- // This is used by Editor.reroutePasteEvent
- var teHack = this.textareaHack = document.createElement("TEXTAREA");
- div.appendChild(teHack);
- teHack.style.position = "absolute";
- teHack.style.left = "-10000px";
- teHack.style.width = "10px";
-
- // Link back to this object, so that the editor can fetch options
- // and add a reference to itself.
- frame.CodeMirror = this;
- if (options.domain && internetExplorer) {
- this.html = frameHTML(options);
- frame.src = "javascript:(function(){document.open();" +
- (options.domain ? "document.domain=\"" + options.domain + "\";" : "") +
- "document.write(window.frameElement.CodeMirror.html);document.close();})()";
- }
- else {
- frame.src = "javascript:false";
- }
-
- if (place.appendChild) place.appendChild(div);
- else place(div);
- div.appendChild(frame);
- if (options.lineNumbers) this.lineNumbers = addLineNumberDiv(div);
-
- this.win = frame.contentWindow;
- if (!options.domain || !internetExplorer) {
- this.win.document.open();
- this.win.document.write(frameHTML(options));
- this.win.document.close();
- }
- }
-
- CodeMirror.prototype = {
- init: function() {
- if (this.options.initCallback) this.options.initCallback(this);
- if (this.options.lineNumbers) this.activateLineNumbers();
- if (this.options.reindentOnLoad) this.reindent();
- if (this.options.height == "dynamic") this.setDynamicHeight();
- },
-
- getCode: function() {return this.editor.getCode();},
- setCode: function(code) {this.editor.importCode(code);},
- selection: function() {this.focusIfIE(); return this.editor.selectedText();},
- reindent: function() {this.editor.reindent();},
- reindentSelection: function() {this.focusIfIE(); this.editor.reindentSelection(null);},
-
- focusIfIE: function() {
- // in IE, a lot of selection-related functionality only works when the frame is focused
- if (this.win.select.ie_selection) this.focus();
- },
- focus: function() {
- this.win.focus();
- if (this.editor.selectionSnapshot) // IE hack
- this.win.select.setBookmark(this.win.document.body, this.editor.selectionSnapshot);
- },
- replaceSelection: function(text) {
- this.focus();
- this.editor.replaceSelection(text);
- return true;
- },
- replaceChars: function(text, start, end) {
- this.editor.replaceChars(text, start, end);
- },
- getSearchCursor: function(string, fromCursor, caseFold) {
- return this.editor.getSearchCursor(string, fromCursor, caseFold);
- },
-
- undo: function() {this.editor.history.undo();},
- redo: function() {this.editor.history.redo();},
- historySize: function() {return this.editor.history.historySize();},
- clearHistory: function() {this.editor.history.clear();},
-
- grabKeys: function(callback, filter) {this.editor.grabKeys(callback, filter);},
- ungrabKeys: function() {this.editor.ungrabKeys();},
-
- setParser: function(name, parserConfig) {this.editor.setParser(name, parserConfig);},
- setSpellcheck: function(on) {this.win.document.body.spellcheck = on;},
- setStylesheet: function(names) {
- if (typeof names === "string") names = [names];
- var activeStylesheets = {};
- var matchedNames = {};
- var links = this.win.document.getElementsByTagName("link");
- // Create hashes of active stylesheets and matched names.
- // This is O(n^2) but n is expected to be very small.
- for (var x = 0, link; link = links[x]; x++) {
- if (link.rel.indexOf("stylesheet") !== -1) {
- for (var y = 0; y < names.length; y++) {
- var name = names[y];
- if (link.href.substring(link.href.length - name.length) === name) {
- activeStylesheets[link.href] = true;
- matchedNames[name] = true;
- }
- }
- }
- }
- // Activate the selected stylesheets and disable the rest.
- for (var x = 0, link; link = links[x]; x++) {
- if (link.rel.indexOf("stylesheet") !== -1) {
- link.disabled = !(link.href in activeStylesheets);
- }
- }
- // Create any new stylesheets.
- for (var y = 0; y < names.length; y++) {
- var name = names[y];
- if (!(name in matchedNames)) {
- var link = this.win.document.createElement("link");
- link.rel = "stylesheet";
- link.type = "text/css";
- link.href = name;
- this.win.document.getElementsByTagName('head')[0].appendChild(link);
- }
- }
- },
- setTextWrapping: function(on) {
- if (on == this.options.textWrapping) return;
- this.win.document.body.style.whiteSpace = on ? "" : "nowrap";
- this.options.textWrapping = on;
- if (this.lineNumbers) {
- this.setLineNumbers(false);
- this.setLineNumbers(true);
- }
- },
- setIndentUnit: function(unit) {this.win.indentUnit = unit;},
- setUndoDepth: function(depth) {this.editor.history.maxDepth = depth;},
- setTabMode: function(mode) {this.options.tabMode = mode;},
- setLineNumbers: function(on) {
- if (on && !this.lineNumbers) {
- this.lineNumbers = addLineNumberDiv(this.wrapping);
- this.activateLineNumbers();
- }
- else if (!on && this.lineNumbers) {
- this.wrapping.removeChild(this.lineNumbers);
- this.wrapping.style.marginLeft = "";
- this.lineNumbers = null;
- }
- },
-
- cursorPosition: function(start) {this.focusIfIE(); return this.editor.cursorPosition(start);},
- firstLine: function() {return this.editor.firstLine();},
- lastLine: function() {return this.editor.lastLine();},
- nextLine: function(line) {return this.editor.nextLine(line);},
- prevLine: function(line) {return this.editor.prevLine(line);},
- lineContent: function(line) {return this.editor.lineContent(line);},
- setLineContent: function(line, content) {this.editor.setLineContent(line, content);},
- removeLine: function(line){this.editor.removeLine(line);},
- insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);},
- selectLines: function(startLine, startOffset, endLine, endOffset) {
- this.win.focus();
- this.editor.selectLines(startLine, startOffset, endLine, endOffset);
- },
- nthLine: function(n) {
- var line = this.firstLine();
- for (; n > 1 && line !== false; n--)
- line = this.nextLine(line);
- return line;
- },
- lineNumber: function(line) {
- var num = 0;
- while (line !== false) {
- num++;
- line = this.prevLine(line);
- }
- return num;
- },
- jumpToLine: function(line) {
- if (typeof line == "number") line = this.nthLine(line);
- this.selectLines(line, 0);
- this.win.focus();
- },
- currentLine: function() { // Deprecated, but still there for backward compatibility
- return this.lineNumber(this.cursorLine());
- },
- cursorLine: function() {
- return this.cursorPosition().line;
- },
- cursorCoords: function(start) {return this.editor.cursorCoords(start);},
-
- activateLineNumbers: function() {
- var frame = this.frame, win = frame.contentWindow, doc = win.document, body = doc.body,
- nums = this.lineNumbers, scroller = nums.firstChild, self = this;
- var barWidth = null;
-
- function sizeBar() {
- if (frame.offsetWidth == 0) return;
- for (var root = frame; root.parentNode; root = root.parentNode);
- if (!nums.parentNode || root != document || !win.Editor) {
- // Clear event handlers (their nodes might already be collected, so try/catch)
- try{clear();}catch(e){}
- clearInterval(sizeInterval);
- return;
- }
-
- /*if (nums.offsetWidth != barWidth) {
- barWidth = nums.offsetWidth;
- frame.parentNode.style.paddingLeft = barWidth + "px";
- }*/
- }
- function doScroll() {
- nums.scrollTop = body.scrollTop || doc.documentElement.scrollTop || 0;
- }
- // Cleanup function, registered by nonWrapping and wrapping.
- var clear = function(){};
- sizeBar();
- var sizeInterval = setInterval(sizeBar, 500);
-
- function ensureEnoughLineNumbers(fill) {
- var lineHeight = scroller.firstChild.offsetHeight;
- if (lineHeight == 0) return;
- var targetHeight = 50 + Math.max(body.offsetHeight, Math.max(frame.offsetHeight, body.scrollHeight || 0)),
- lastNumber = Math.ceil(targetHeight / lineHeight);
- for (var i = scroller.childNodes.length; i <= lastNumber; i++) {
- var div = document.createElement("DIV");
- div.appendChild(document.createTextNode(fill ? String(i + 1) : "\u00a0"));
- scroller.appendChild(div);
- }
- }
-
- function nonWrapping() {
- function update() {
- ensureEnoughLineNumbers(true);
- doScroll();
- }
- self.updateNumbers = update;
- var onScroll = win.addEventHandler(win, "scroll", doScroll, true),
- onResize = win.addEventHandler(win, "resize", update, true);
- clear = function(){
- onScroll(); onResize();
- if (self.updateNumbers == update) self.updateNumbers = null;
- };
- update();
- }
-
- function wrapping() {
- var node, lineNum, next, pos, changes = [], styleNums = self.options.styleNumbers;
-
- function setNum(n, node) {
- // Does not typically happen (but can, if you mess with the
- // document during the numbering)
- if (!lineNum) lineNum = scroller.appendChild(document.createElement("DIV"));
- if (styleNums) styleNums(lineNum, node, n);
- // Changes are accumulated, so that the document layout
- // doesn't have to be recomputed during the pass
- changes.push(lineNum); changes.push(n);
- pos = lineNum.offsetHeight + lineNum.offsetTop;
- lineNum = lineNum.nextSibling;
- }
- function commitChanges() {
- for (var i = 0; i < changes.length; i += 2)
- changes[i].innerHTML = changes[i + 1];
- changes = [];
- }
- function work() {
- if (!scroller.parentNode || scroller.parentNode != self.lineNumbers) return;
-
- var endTime = new Date().getTime() + self.options.lineNumberTime;
- while (node) {
- setNum(next++, node.previousSibling);
- for (; node && !win.isBR(node); node = node.nextSibling) {
- var bott = node.offsetTop + node.offsetHeight;
- while (scroller.offsetHeight && bott - 3 > pos) setNum(" ");
- }
- if (node) node = node.nextSibling;
- if (new Date().getTime() > endTime) {
- commitChanges();
- pending = setTimeout(work, self.options.lineNumberDelay);
- return;
- }
- }
- while (lineNum) setNum(next++);
- commitChanges();
- doScroll();
- }
- function start(firstTime) {
- doScroll();
- ensureEnoughLineNumbers(firstTime);
- node = body.firstChild;
- lineNum = scroller.firstChild;
- pos = 0;
- next = 1;
- work();
- }
-
- start(true);
- var pending = null;
- function update() {
- if (pending) clearTimeout(pending);
- if (self.editor.allClean()) start();
- else pending = setTimeout(update, 200);
- }
- self.updateNumbers = update;
- var onScroll = win.addEventHandler(win, "scroll", doScroll, true),
- onResize = win.addEventHandler(win, "resize", update, true);
- clear = function(){
- if (pending) clearTimeout(pending);
- if (self.updateNumbers == update) self.updateNumbers = null;
- onScroll();
- onResize();
- };
- }
- (this.options.textWrapping || this.options.styleNumbers ? wrapping : nonWrapping)();
- },
-
- setDynamicHeight: function() {
- var self = this, activity = self.options.cursorActivity, win = self.win, body = win.document.body,
- lineHeight = null, timeout = null, vmargin = 2 * self.frame.offsetTop;
- body.style.overflowY = "hidden";
- win.document.documentElement.style.overflowY = "hidden";
- this.frame.scrolling = "no";
-
- function updateHeight() {
- for (var span = body.firstChild, sawBR = false; span; span = span.nextSibling)
- if (win.isSpan(span) && span.offsetHeight) {
- lineHeight = span.offsetHeight;
- if (!sawBR) vmargin = 2 * (self.frame.offsetTop + span.offsetTop + body.offsetTop + (internetExplorer ? 10 : 0));
- break;
- }
- if (lineHeight)
- self.wrapping.style.height = Math.max(vmargin + lineHeight * (body.getElementsByTagName("BR").length + 1),
- self.options.minHeight) + "px";
- }
- setTimeout(updateHeight, 100);
- self.options.cursorActivity = function(x) {
- if (activity) activity(x);
- clearTimeout(timeout);
- timeout = setTimeout(updateHeight, 200);
- };
- }
- };
-
- CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}};
-
- CodeMirror.replace = function(element) {
- if (typeof element == "string")
- element = document.getElementById(element);
- return function(newElement) {
- element.parentNode.replaceChild(newElement, element);
- };
- };
-
- CodeMirror.fromTextArea = function(area, options) {
- if (typeof area == "string")
- area = document.getElementById(area);
-
- options = options || {};
- if (area.style.width && options.width == null)
- options.width = area.style.width;
- if (area.style.height && options.height == null)
- options.height = area.style.height;
- if (options.content == null) options.content = area.value;
-
- if (area.form) {
- function updateField() {
- area.value = mirror.getCode();
- }
- if (typeof area.form.addEventListener == "function")
- area.form.addEventListener("submit", updateField, false);
- else
- area.form.attachEvent("onsubmit", updateField);
- var realSubmit = area.form.submit;
- function wrapSubmit() {
- updateField();
- // Can't use realSubmit.apply because IE6 is too stupid
- area.form.submit = realSubmit;
- area.form.submit();
- area.form.submit = wrapSubmit;
- }
- area.form.submit = wrapSubmit;
- }
-
- function insert(frame) {
- if (area.nextSibling)
- area.parentNode.insertBefore(frame, area.nextSibling);
- else
- area.parentNode.appendChild(frame);
- }
-
- area.style.display = "none";
- var mirror = new CodeMirror(insert, options);
- mirror.toTextArea = function() {
- area.parentNode.removeChild(mirror.wrapping);
- area.style.display = "";
- if (area.form) {
- area.form.submit = realSubmit;
- if (typeof area.form.removeEventListener == "function")
- area.form.removeEventListener("submit", updateField, false);
- else
- area.form.detachEvent("onsubmit", updateField);
- }
- };
-
- return mirror;
- };
-
- CodeMirror.isProbablySupported = function() {
- // This is rather awful, but can be useful.
- var match;
- if (window.opera)
- return Number(window.opera.version()) >= 9.52;
- else if (/Apple Computers, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./)))
- return Number(match[1]) >= 3;
- else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/)))
- return Number(match[1]) >= 6;
- else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i))
- return Number(match[1]) >= 20050901;
- else if (match = navigator.userAgent.match(/AppleWebKit\/(\d+)/))
- return Number(match[1]) >= 525;
- else
- return null;
- };
-
- return CodeMirror;
-})();
+++ /dev/null
-/* The Editor object manages the content of the editable frame. It
- * catches events, colours nodes, and indents lines. This file also
- * holds some functions for transforming arbitrary DOM structures into
- * plain sequences of <span> and <br> elements
- */
-
-var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
-var webkit = /AppleWebKit/.test(navigator.userAgent);
-var safari = /Apple Computers, Inc/.test(navigator.vendor);
-var gecko = /gecko\/(\d{8})/i.test(navigator.userAgent);
-// TODO this is related to the backspace-at-end-of-line bug. Remove
-// this if Opera gets their act together, make the version check more
-// broad if they don't.
-var brokenOpera = window.opera && /Version\/10.[56]/.test(navigator.userAgent);
-
-// Make sure a string does not contain two consecutive 'collapseable'
-// whitespace characters.
-function makeWhiteSpace(n) {
- var buffer = [], nb = true;
- for (; n > 0; n--) {
- buffer.push((nb || n == 1) ? nbsp : " ");
- nb ^= true;
- }
- return buffer.join("");
-}
-
-// Create a set of white-space characters that will not be collapsed
-// by the browser, but will not break text-wrapping either.
-function fixSpaces(string) {
- if (string.charAt(0) == " ") string = nbsp + string.slice(1);
- return string.replace(/\t/g, function() {return makeWhiteSpace(indentUnit);})
- .replace(/[ \u00a0]{2,}/g, function(s) {return makeWhiteSpace(s.length);});
-}
-
-function cleanText(text) {
- return text.replace(/\u00a0/g, " ");
-}
-
-// Create a SPAN node with the expected properties for document part
-// spans.
-function makePartSpan(value, doc) {
- var text = value;
- if (value.nodeType == 3) text = value.nodeValue;
- else value = doc.createTextNode(text);
-
- var span = doc.createElement("SPAN");
- span.isPart = true;
- span.appendChild(value);
- span.currentText = text;
- return span;
-}
-
-var Editor = (function(){
- // The HTML elements whose content should be suffixed by a newline
- // when converting them to flat text.
- var newlineElements = {"P": true, "DIV": true, "LI": true};
-
- function asEditorLines(string) {
- var tab = makeWhiteSpace(indentUnit);
- return map(string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n").split("\n"), fixSpaces);
- }
-
- // Helper function for traverseDOM. Flattens an arbitrary DOM node
- // into an array of textnodes and <br> tags.
- function simplifyDOM(root, atEnd) {
- var doc = root.ownerDocument;
- var result = [];
- var leaving = true;
-
- function simplifyNode(node, top) {
- if (node.nodeType == 3) {
- var text = node.nodeValue = fixSpaces(node.nodeValue.replace(/\r/g, "").replace(/\n/g, " "));
- if (text.length) leaving = false;
- result.push(node);
- }
- else if (isBR(node) && node.childNodes.length == 0) {
- leaving = true;
- result.push(node);
- }
- else {
- for (var n = node.firstChild; n; n = n.nextSibling) simplifyNode(n);
- if (!leaving && newlineElements.hasOwnProperty(node.nodeName.toUpperCase())) {
- leaving = true;
- if (!atEnd || !top)
- result.push(doc.createElement("BR"));
- }
- }
- }
-
- simplifyNode(root, true);
- return result;
- }
-
- // Creates a MochiKit-style iterator that goes over a series of DOM
- // nodes. The values it yields are strings, the textual content of
- // the nodes. It makes sure that all nodes up to and including the
- // one whose text is being yielded have been 'normalized' to be just
- // <span> and <br> elements.
- function traverseDOM(start){
- var owner = start.ownerDocument;
- var nodeQueue = [];
-
- // Create a function that can be used to insert nodes after the
- // one given as argument.
- function pointAt(node){
- var parent = node.parentNode;
- var next = node.nextSibling;
- return function(newnode) {
- parent.insertBefore(newnode, next);
- };
- }
- var point = null;
-
- // This an Opera-specific hack -- always insert an empty span
- // between two BRs, because Opera's cursor code gets terribly
- // confused when the cursor is between two BRs.
- var afterBR = true;
-
- // Insert a normalized node at the current point. If it is a text
- // node, wrap it in a <span>, and give that span a currentText
- // property -- this is used to cache the nodeValue, because
- // directly accessing nodeValue is horribly slow on some browsers.
- // The dirty property is used by the highlighter to determine
- // which parts of the document have to be re-highlighted.
- function insertPart(part){
- var text = "\n";
- if (part.nodeType == 3) {
- select.snapshotChanged();
- part = makePartSpan(part, owner);
- text = part.currentText;
- afterBR = false;
- }
- else {
- if (afterBR && window.opera)
- point(makePartSpan("", owner));
- afterBR = true;
- }
- part.dirty = true;
- nodeQueue.push(part);
- point(part);
- return text;
- }
-
- // Extract the text and newlines from a DOM node, insert them into
- // the document, and return the textual content. Used to replace
- // non-normalized nodes.
- function writeNode(node, end) {
- var simplified = simplifyDOM(node, end);
- for (var i = 0; i < simplified.length; i++)
- simplified[i] = insertPart(simplified[i]);
- return simplified.join("");
- }
-
- // Check whether a node is a normalized <span> element.
- function partNode(node){
- if (node.isPart && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
- node.currentText = node.firstChild.nodeValue;
- return !/[\n\t\r]/.test(node.currentText);
- }
- return false;
- }
-
- // Advance to next node, return string for current node.
- function next() {
- if (!start) throw StopIteration;
- var node = start;
- start = node.nextSibling;
-
- if (partNode(node)){
- nodeQueue.push(node);
- afterBR = false;
- return node.currentText;
- }
- else if (isBR(node)) {
- if (afterBR && window.opera)
- node.parentNode.insertBefore(makePartSpan("", owner), node);
- nodeQueue.push(node);
- afterBR = true;
- return "\n";
- }
- else {
- var end = !node.nextSibling;
- point = pointAt(node);
- removeElement(node);
- return writeNode(node, end);
- }
- }
-
- // MochiKit iterators are objects with a next function that
- // returns the next value or throws StopIteration when there are
- // no more values.
- return {next: next, nodes: nodeQueue};
- }
-
- // Determine the text size of a processed node.
- function nodeSize(node) {
- return isBR(node) ? 1 : node.currentText.length;
- }
-
- // Search backwards through the top-level nodes until the next BR or
- // the start of the frame.
- function startOfLine(node) {
- while (node && !isBR(node)) node = node.previousSibling;
- return node;
- }
- function endOfLine(node, container) {
- if (!node) node = container.firstChild;
- else if (isBR(node)) node = node.nextSibling;
-
- while (node && !isBR(node)) node = node.nextSibling;
- return node;
- }
-
- function time() {return new Date().getTime();}
-
- // Client interface for searching the content of the editor. Create
- // these by calling CodeMirror.getSearchCursor. To use, call
- // findNext on the resulting object -- this returns a boolean
- // indicating whether anything was found, and can be called again to
- // skip to the next find. Use the select and replace methods to
- // actually do something with the found locations.
- function SearchCursor(editor, string, fromCursor, caseFold) {
- this.editor = editor;
- if (caseFold == undefined) {
- caseFold = (string == string.toLowerCase());
- }
- this.caseFold = caseFold;
- if (caseFold) string = string.toLowerCase();
- this.history = editor.history;
- this.history.commit();
-
- // Are we currently at an occurrence of the search string?
- this.atOccurrence = false;
- // The object stores a set of nodes coming after its current
- // position, so that when the current point is taken out of the
- // DOM tree, we can still try to continue.
- this.fallbackSize = 15;
- var cursor;
- // Start from the cursor when specified and a cursor can be found.
- if (fromCursor && (cursor = select.cursorPos(this.editor.container))) {
- this.line = cursor.node;
- this.offset = cursor.offset;
- }
- else {
- this.line = null;
- this.offset = 0;
- }
- this.valid = !!string;
-
- // Create a matcher function based on the kind of string we have.
- var target = string.split("\n"), self = this;
- this.matches = (target.length == 1) ?
- // For one-line strings, searching can be done simply by calling
- // indexOf on the current line.
- function() {
- var line = cleanText(self.history.textAfter(self.line).slice(self.offset));
- var match = (self.caseFold ? line.toLowerCase() : line).indexOf(string);
- if (match > -1)
- return {from: {node: self.line, offset: self.offset + match},
- to: {node: self.line, offset: self.offset + match + string.length}};
- } :
- // Multi-line strings require internal iteration over lines, and
- // some clunky checks to make sure the first match ends at the
- // end of the line and the last match starts at the start.
- function() {
- var firstLine = cleanText(self.history.textAfter(self.line).slice(self.offset));
- var match = (self.caseFold ? firstLine.toLowerCase() : firstLine).lastIndexOf(target[0]);
- if (match == -1 || match != firstLine.length - target[0].length)
- return false;
- var startOffset = self.offset + match;
-
- var line = self.history.nodeAfter(self.line);
- for (var i = 1; i < target.length - 1; i++) {
- var lineText = cleanText(self.history.textAfter(line));
- if ((self.caseFold ? lineText.toLowerCase() : lineText) != target[i])
- return false;
- line = self.history.nodeAfter(line);
- }
-
- var lastLine = cleanText(self.history.textAfter(line));
- if ((self.caseFold ? lastLine.toLowerCase() : lastLine).indexOf(target[target.length - 1]) != 0)
- return false;
-
- return {from: {node: self.line, offset: startOffset},
- to: {node: line, offset: target[target.length - 1].length}};
- };
- }
-
- SearchCursor.prototype = {
- findNext: function() {
- if (!this.valid) return false;
- this.atOccurrence = false;
- var self = this;
-
- // Go back to the start of the document if the current line is
- // no longer in the DOM tree.
- if (this.line && !this.line.parentNode) {
- this.line = null;
- this.offset = 0;
- }
-
- // Set the cursor's position one character after the given
- // position.
- function saveAfter(pos) {
- if (self.history.textAfter(pos.node).length > pos.offset) {
- self.line = pos.node;
- self.offset = pos.offset + 1;
- }
- else {
- self.line = self.history.nodeAfter(pos.node);
- self.offset = 0;
- }
- }
-
- while (true) {
- var match = this.matches();
- // Found the search string.
- if (match) {
- this.atOccurrence = match;
- saveAfter(match.from);
- return true;
- }
- this.line = this.history.nodeAfter(this.line);
- this.offset = 0;
- // End of document.
- if (!this.line) {
- this.valid = false;
- return false;
- }
- }
- },
-
- select: function() {
- if (this.atOccurrence) {
- select.setCursorPos(this.editor.container, this.atOccurrence.from, this.atOccurrence.to);
- select.scrollToCursor(this.editor.container);
- }
- },
-
- replace: function(string) {
- if (this.atOccurrence) {
- var end = this.editor.replaceRange(this.atOccurrence.from, this.atOccurrence.to, string);
- this.line = end.node;
- this.offset = end.offset;
- this.atOccurrence = false;
- }
- }
- };
-
- // The Editor object is the main inside-the-iframe interface.
- function Editor(options) {
- this.options = options;
- window.indentUnit = options.indentUnit;
- this.parent = parent;
- this.doc = document;
- var container = this.container = this.doc.body;
- this.win = window;
- this.history = new UndoHistory(container, options.undoDepth, options.undoDelay, this);
- var self = this;
-
- if (!Editor.Parser)
- throw "No parser loaded.";
- if (options.parserConfig && Editor.Parser.configure)
- Editor.Parser.configure(options.parserConfig);
-
- if (!options.readOnly)
- select.setCursorPos(container, {node: null, offset: 0});
-
- this.dirty = [];
- this.importCode(options.content || "");
- this.history.onChange = options.onChange;
-
- if (!options.readOnly) {
- if (options.continuousScanning !== false) {
- this.scanner = this.documentScanner(options.passTime);
- this.delayScanning();
- }
-
- function setEditable() {
- // Use contentEditable instead of designMode on IE, since designMode frames
- // can not run any scripts. It would be nice if we could use contentEditable
- // everywhere, but it is significantly flakier than designMode on every
- // single non-IE browser.
- if (document.body.contentEditable != undefined && internetExplorer)
- document.body.contentEditable = "true";
- else
- document.designMode = "on";
-
- document.documentElement.style.borderWidth = "0";
- if (!options.textWrapping)
- container.style.whiteSpace = "nowrap";
- }
-
- // If setting the frame editable fails, try again when the user
- // focus it (happens when the frame is not visible on
- // initialisation, in Firefox).
- try {
- setEditable();
- }
- catch(e) {
- var focusEvent = addEventHandler(document, "focus", function() {
- focusEvent();
- setEditable();
- }, true);
- }
-
- addEventHandler(document, "keydown", method(this, "keyDown"));
- addEventHandler(document, "keypress", method(this, "keyPress"));
- addEventHandler(document, "keyup", method(this, "keyUp"));
-
- function cursorActivity() {self.cursorActivity(false);}
- addEventHandler(document.body, "mouseup", cursorActivity);
- addEventHandler(document.body, "cut", cursorActivity);
-
- // workaround for a gecko bug [?] where going forward and then
- // back again breaks designmode (no more cursor)
- if (gecko)
- addEventHandler(this.win, "pagehide", function(){self.unloaded = true;});
-
- addEventHandler(document.body, "paste", function(event) {
- cursorActivity();
- var text = null;
- try {
- var clipboardData = event.clipboardData || window.clipboardData;
- if (clipboardData) text = clipboardData.getData('Text');
- }
- catch(e) {}
- if (text !== null) {
- event.stop();
- self.replaceSelection(text);
- select.scrollToCursor(self.container);
- }
- });
-
- if (this.options.autoMatchParens)
- addEventHandler(document.body, "click", method(this, "scheduleParenHighlight"));
- }
- else if (!options.textWrapping) {
- container.style.whiteSpace = "nowrap";
- }
- }
-
- function isSafeKey(code) {
- return (code >= 16 && code <= 18) || // shift, control, alt
- (code >= 33 && code <= 40); // arrows, home, end
- }
-
- Editor.prototype = {
- // Import a piece of code into the editor.
- importCode: function(code) {
- this.history.push(null, null, asEditorLines(code));
- this.history.reset();
- },
-
- // Extract the code from the editor.
- getCode: function() {
- if (!this.container.firstChild)
- return "";
-
- var accum = [];
- select.markSelection(this.win);
- forEach(traverseDOM(this.container.firstChild), method(accum, "push"));
- select.selectMarked();
- return cleanText(accum.join(""));
- },
-
- checkLine: function(node) {
- if (node === false || !(node == null || node.parentNode == this.container))
- throw parent.CodeMirror.InvalidLineHandle;
- },
-
- cursorPosition: function(start) {
- if (start == null) start = true;
- var pos = select.cursorPos(this.container, start);
- if (pos) return {line: pos.node, character: pos.offset};
- else return {line: null, character: 0};
- },
-
- firstLine: function() {
- return null;
- },
-
- lastLine: function() {
- if (this.container.lastChild) return startOfLine(this.container.lastChild);
- else return null;
- },
-
- nextLine: function(line) {
- this.checkLine(line);
- var end = endOfLine(line, this.container);
- return end || false;
- },
-
- prevLine: function(line) {
- this.checkLine(line);
- if (line == null) return false;
- return startOfLine(line.previousSibling);
- },
-
- visibleLineCount: function() {
- var line = this.container.firstChild;
- while (line && isBR(line)) line = line.nextSibling; // BR heights are unreliable
- if (!line) return false;
- var innerHeight = (window.innerHeight
- || document.documentElement.clientHeight
- || document.body.clientHeight);
- return Math.floor(innerHeight / line.offsetHeight);
- },
-
- selectLines: function(startLine, startOffset, endLine, endOffset) {
- this.checkLine(startLine);
- var start = {node: startLine, offset: startOffset}, end = null;
- if (endOffset !== undefined) {
- this.checkLine(endLine);
- end = {node: endLine, offset: endOffset};
- }
- select.setCursorPos(this.container, start, end);
- select.scrollToCursor(this.container);
- },
-
- lineContent: function(line) {
- var accum = [];
- for (line = line ? line.nextSibling : this.container.firstChild;
- line && !isBR(line); line = line.nextSibling)
- accum.push(nodeText(line));
- return cleanText(accum.join(""));
- },
-
- setLineContent: function(line, content) {
- this.history.commit();
- this.replaceRange({node: line, offset: 0},
- {node: line, offset: this.history.textAfter(line).length},
- content);
- this.addDirtyNode(line);
- this.scheduleHighlight();
- },
-
- removeLine: function(line) {
- var node = line ? line.nextSibling : this.container.firstChild;
- while (node) {
- var next = node.nextSibling;
- removeElement(node);
- if (isBR(node)) break;
- node = next;
- }
- this.addDirtyNode(line);
- this.scheduleHighlight();
- },
-
- insertIntoLine: function(line, position, content) {
- var before = null;
- if (position == "end") {
- before = endOfLine(line, this.container);
- }
- else {
- for (var cur = line ? line.nextSibling : this.container.firstChild; cur; cur = cur.nextSibling) {
- if (position == 0) {
- before = cur;
- break;
- }
- var text = nodeText(cur);
- if (text.length > position) {
- before = cur.nextSibling;
- content = text.slice(0, position) + content + text.slice(position);
- removeElement(cur);
- break;
- }
- position -= text.length;
- }
- }
-
- var lines = asEditorLines(content), doc = this.container.ownerDocument;
- for (var i = 0; i < lines.length; i++) {
- if (i > 0) this.container.insertBefore(doc.createElement("BR"), before);
- this.container.insertBefore(makePartSpan(lines[i], doc), before);
- }
- this.addDirtyNode(line);
- this.scheduleHighlight();
- },
-
- // Retrieve the selected text.
- selectedText: function() {
- var h = this.history;
- h.commit();
-
- var start = select.cursorPos(this.container, true),
- end = select.cursorPos(this.container, false);
- if (!start || !end) return "";
-
- if (start.node == end.node)
- return h.textAfter(start.node).slice(start.offset, end.offset);
-
- var text = [h.textAfter(start.node).slice(start.offset)];
- for (var pos = h.nodeAfter(start.node); pos != end.node; pos = h.nodeAfter(pos))
- text.push(h.textAfter(pos));
- text.push(h.textAfter(end.node).slice(0, end.offset));
- return cleanText(text.join("\n"));
- },
-
- // Replace the selection with another piece of text.
- replaceSelection: function(text) {
- this.history.commit();
-
- var start = select.cursorPos(this.container, true),
- end = select.cursorPos(this.container, false);
- if (!start || !end) return;
-
- end = this.replaceRange(start, end, text);
- select.setCursorPos(this.container, end);
- },
-
- cursorCoords: function(start) {
- var sel = select.cursorPos(this.container, start);
- if (!sel) return null;
- var off = sel.offset, node = sel.node, doc = this.win.document, self = this;
- function measureFromNode(node, xOffset) {
- var y = -(self.win.document.body.scrollTop || self.win.document.documentElement.scrollTop || 0),
- x = -(self.win.document.body.scrollLeft || self.win.document.documentElement.scrollLeft || 0) + xOffset;
- forEach([node, self.win.frameElement], function(n) {
- while (n) {x += n.offsetLeft; y += n.offsetTop;n = n.offsetParent;}
- });
- return {x: x, y: y, yBot: y + node.offsetHeight};
- }
- function withTempNode(text, f) {
- var node = doc.createElement("SPAN");
- node.appendChild(doc.createTextNode(text));
- try {return f(node);}
- finally {if (node.parentNode) node.parentNode.removeChild(node);}
- }
-
- while (off) {
- node = node ? node.nextSibling : this.container.firstChild;
- var txt = nodeText(node);
- if (off < txt.length)
- return withTempNode(txt.substr(0, off), function(tmp) {
- tmp.style.position = "absolute"; tmp.style.visibility = "hidden";
- tmp.className = node.className;
- self.container.appendChild(tmp);
- return measureFromNode(node, tmp.offsetWidth);
- });
- off -= txt.length;
- }
- if (node && isSpan(node))
- return measureFromNode(node, node.offsetWidth);
- else if (node && node.nextSibling && isSpan(node.nextSibling))
- return measureFromNode(node.nextSibling, 0);
- else
- return withTempNode("\u200b", function(tmp) {
- if (node) node.parentNode.insertBefore(tmp, node.nextSibling);
- else self.container.insertBefore(tmp, self.container.firstChild);
- return measureFromNode(tmp, 0);
- });
- },
-
- reroutePasteEvent: function() {
- if (this.capturingPaste || window.opera) return;
- this.capturingPaste = true;
- var te = window.frameElement.CodeMirror.textareaHack;
- parent.focus();
- te.value = "";
- te.focus();
-
- var self = this;
- this.parent.setTimeout(function() {
- self.capturingPaste = false;
- self.win.focus();
- if (self.selectionSnapshot) // IE hack
- self.win.select.setBookmark(self.container, self.selectionSnapshot);
- var text = te.value;
- if (text) {
- self.replaceSelection(text);
- select.scrollToCursor(self.container);
- }
- }, 10);
- },
-
- replaceRange: function(from, to, text) {
- var lines = asEditorLines(text);
- lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0];
- var lastLine = lines[lines.length - 1];
- lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset);
- var end = this.history.nodeAfter(to.node);
- this.history.push(from.node, end, lines);
- return {node: this.history.nodeBefore(end),
- offset: lastLine.length};
- },
-
- getSearchCursor: function(string, fromCursor, caseFold) {
- return new SearchCursor(this, string, fromCursor, caseFold);
- },
-
- // Re-indent the whole buffer
- reindent: function() {
- if (this.container.firstChild)
- this.indentRegion(null, this.container.lastChild);
- },
-
- reindentSelection: function(direction) {
- if (!select.somethingSelected(this.win)) {
- this.indentAtCursor(direction);
- }
- else {
- var start = select.selectionTopNode(this.container, true),
- end = select.selectionTopNode(this.container, false);
- if (start === false || end === false) return;
- this.indentRegion(start, end, direction);
- }
- },
-
- grabKeys: function(eventHandler, filter) {
- this.frozen = eventHandler;
- this.keyFilter = filter;
- },
- ungrabKeys: function() {
- this.frozen = "leave";
- },
-
- setParser: function(name, parserConfig) {
- Editor.Parser = window[name];
- parserConfig = parserConfig || this.options.parserConfig;
- if (parserConfig && Editor.Parser.configure)
- Editor.Parser.configure(parserConfig);
-
- if (this.container.firstChild) {
- forEach(this.container.childNodes, function(n) {
- if (n.nodeType != 3) n.dirty = true;
- });
- this.addDirtyNode(this.firstChild);
- this.scheduleHighlight();
- }
- },
-
- // Intercept enter and tab, and assign their new functions.
- keyDown: function(event) {
- if (this.frozen == "leave") {this.frozen = null; this.keyFilter = null;}
- if (this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode, event))) {
- event.stop();
- this.frozen(event);
- return;
- }
-
- var code = this.lastKeyDownCode = event.keyCode;
- // Don't scan when the user is typing.
- this.delayScanning();
- // Schedule a paren-highlight event, if configured.
- if (this.options.autoMatchParens)
- this.scheduleParenHighlight();
-
- // The various checks for !altKey are there because AltGr sets both
- // ctrlKey and altKey to true, and should not be recognised as
- // Control.
- if (code == 13) { // enter
- if (event.ctrlKey && !event.altKey) {
- this.reparseBuffer();
- }
- else {
- select.insertNewlineAtCursor(this.win);
- this.indentAtCursor();
- select.scrollToCursor(this.container);
- }
- event.stop();
- }
- else if (code == 9 && this.options.tabMode != "default" && !event.ctrlKey) { // tab
- this.handleTab(!event.shiftKey);
- event.stop();
- }
- else if (code == 32 && event.shiftKey && this.options.tabMode == "default") { // space
- this.handleTab(true);
- event.stop();
- }
- else if (code == 36 && !event.shiftKey && !event.ctrlKey) { // home
- if (this.home()) event.stop();
- }
- else if (code == 35 && !event.shiftKey && !event.ctrlKey) { // end
- if (this.end()) event.stop();
- }
- // Only in Firefox is the default behavior for PgUp/PgDn correct.
- else if (code == 33 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgUp
- if (this.pageUp()) event.stop();
- }
- else if (code == 34 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgDn
- if (this.pageDown()) event.stop();
- }
- else if ((code == 219 || code == 221) && event.ctrlKey && !event.altKey) { // [, ]
- this.highlightParens(event.shiftKey, true);
- event.stop();
- }
- else if (event.metaKey && !event.shiftKey && (code == 37 || code == 39)) { // Meta-left/right
- var cursor = select.selectionTopNode(this.container);
- if (cursor !== false && this.container.firstChild) {
- if (code == 37) select.focusAfterNode(startOfLine(cursor), this.container);
- else {
- var end = endOfLine(cursor, this.container);
- select.focusAfterNode(end ? end.previousSibling : this.container.lastChild, this.container);
- }
- event.stop();
- }
- }
- else if ((event.ctrlKey || event.metaKey) && !event.altKey) {
- if ((event.shiftKey && code == 90) || code == 89) { // shift-Z, Y
- select.scrollToNode(this.history.redo());
- event.stop();
- }
- else if (code == 90 || (safari && code == 8)) { // Z, backspace
- select.scrollToNode(this.history.undo());
- event.stop();
- }
- else if (code == 83 && this.options.saveFunction) { // S
- this.options.saveFunction();
- event.stop();
- }
- else if (internetExplorer && code == 86) {
- this.reroutePasteEvent();
- }
- }
- this.keyUpOrPressAfterLastKeyDown = false;
- },
-
- // Check for characters that should re-indent the current line,
- // and prevent Opera from handling enter and tab anyway.
- keyPress: function(event) {
- this.keyUpOrPressAfterLastKeyDown = true;
- var electric = Editor.Parser.electricChars, self = this;
- // Hack for Opera, and Firefox on OS X, in which stopping a
- // keydown event does not prevent the associated keypress event
- // from happening, so we have to cancel enter and tab again
- // here.
- if ((this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode || event.code, event))) ||
- event.code == 13 || (event.code == 9 && this.options.tabMode != "default") ||
- (event.code == 32 && event.shiftKey && this.options.tabMode == "default"))
- event.stop();
- else if (electric && electric.indexOf(event.character) != -1)
- this.parent.setTimeout(function(){self.indentAtCursor(null);}, 0);
- else if ((event.character == "v" || event.character == "V")
- && (event.ctrlKey || event.metaKey) && !event.altKey) // ctrl-V
- this.reroutePasteEvent();
- // Work around a bug where pressing backspace at the end of a
- // line often causes the cursor to jump to the start of the line
- // in Opera 10.60.
- else if (brokenOpera && event.code == 8) {
- var sel = select.selectionTopNode(this.container), self = this,
- next = sel ? sel.nextSibling : this.container.firstChild;
- if (sel !== false && next && isBR(next))
- this.parent.setTimeout(function(){
- if (select.selectionTopNode(self.container) == next)
- select.focusAfterNode(next.previousSibling, self.container);
- }, 20);
- }
- },
-
- // Mark the node at the cursor dirty when a non-safe key is
- // released.
- keyUp: function(event) {
- this.keyUpOrPressAfterLastKeyDown = true;
- this.cursorActivity(isSafeKey(event.keyCode));
- },
-
- // Indent the line following a given <br>, or null for the first
- // line. If given a <br> element, this must have been highlighted
- // so that it has an indentation method. Returns the whitespace
- // element that has been modified or created (if any).
- indentLineAfter: function(start, direction) {
- // whiteSpace is the whitespace span at the start of the line,
- // or null if there is no such node.
- var whiteSpace = start ? start.nextSibling : this.container.firstChild;
- if (whiteSpace && !hasClass(whiteSpace, "whitespace"))
- whiteSpace = null;
-
- // Sometimes the start of the line can influence the correct
- // indentation, so we retrieve it.
- var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
- var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : "";
-
- // Ask the lexical context for the correct indentation, and
- // compute how much this differs from the current indentation.
- var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0;
- if (direction != null && this.options.tabMode == "shift")
- newIndent = direction ? curIndent + indentUnit : Math.max(0, curIndent - indentUnit)
- else if (start)
- newIndent = start.indentation(nextChars, curIndent, direction);
- else if (Editor.Parser.firstIndentation)
- newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction);
- var indentDiff = newIndent - curIndent;
-
- // If there is too much, this is just a matter of shrinking a span.
- if (indentDiff < 0) {
- if (newIndent == 0) {
- if (firstText) select.snapshotMove(whiteSpace.firstChild, firstText.firstChild, 0);
- removeElement(whiteSpace);
- whiteSpace = null;
- }
- else {
- select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true);
- whiteSpace.currentText = makeWhiteSpace(newIndent);
- whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
- }
- }
- // Not enough...
- else if (indentDiff > 0) {
- // If there is whitespace, we grow it.
- if (whiteSpace) {
- whiteSpace.currentText = makeWhiteSpace(newIndent);
- whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
- select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true);
- }
- // Otherwise, we have to add a new whitespace node.
- else {
- whiteSpace = makePartSpan(makeWhiteSpace(newIndent), this.doc);
- whiteSpace.className = "whitespace";
- if (start) insertAfter(whiteSpace, start);
- else this.container.insertBefore(whiteSpace, this.container.firstChild);
- select.snapshotMove(firstText && (firstText.firstChild || firstText),
- whiteSpace.firstChild, newIndent, false, true);
- }
- }
- if (indentDiff != 0) this.addDirtyNode(start);
- },
-
- // Re-highlight the selected part of the document.
- highlightAtCursor: function() {
- var pos = select.selectionTopNode(this.container, true);
- var to = select.selectionTopNode(this.container, false);
- if (pos === false || to === false) return false;
-
- select.markSelection(this.win);
- if (this.highlight(pos, endOfLine(to, this.container), true, 20) === false)
- return false;
- select.selectMarked();
- return true;
- },
-
- // When tab is pressed with text selected, the whole selection is
- // re-indented, when nothing is selected, the line with the cursor
- // is re-indented.
- handleTab: function(direction) {
- if (this.options.tabMode == "spaces")
- select.insertTabAtCursor(this.win);
- else
- this.reindentSelection(direction);
- },
-
- // Custom home behaviour that doesn't land the cursor in front of
- // leading whitespace unless pressed twice.
- home: function() {
- var cur = select.selectionTopNode(this.container, true), start = cur;
- if (cur === false || !(!cur || cur.isPart || isBR(cur)) || !this.container.firstChild)
- return false;
-
- while (cur && !isBR(cur)) cur = cur.previousSibling;
- var next = cur ? cur.nextSibling : this.container.firstChild;
- if (next && next != start && next.isPart && hasClass(next, "whitespace"))
- select.focusAfterNode(next, this.container);
- else
- select.focusAfterNode(cur, this.container);
-
- select.scrollToCursor(this.container);
- return true;
- },
-
- // Some browsers (Opera) don't manage to handle the end key
- // properly in the face of vertical scrolling.
- end: function() {
- var cur = select.selectionTopNode(this.container, true);
- if (cur === false) return false;
- cur = endOfLine(cur, this.container);
- if (!cur) return false;
- select.focusAfterNode(cur.previousSibling, this.container);
- select.scrollToCursor(this.container);
- return true;
- },
-
- pageUp: function() {
- var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
- if (line === false || scrollAmount === false) return false;
- // Try to keep one line on the screen.
- scrollAmount -= 2;
- for (var i = 0; i < scrollAmount; i++) {
- line = this.prevLine(line);
- if (line === false) break;
- }
- if (i == 0) return false; // Already at first line
- select.setCursorPos(this.container, {node: line, offset: 0});
- select.scrollToCursor(this.container);
- return true;
- },
-
- pageDown: function() {
- var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
- if (line === false || scrollAmount === false) return false;
- // Try to move to the last line of the current page.
- scrollAmount -= 2;
- for (var i = 0; i < scrollAmount; i++) {
- var nextLine = this.nextLine(line);
- if (nextLine === false) break;
- line = nextLine;
- }
- if (i == 0) return false; // Already at last line
- select.setCursorPos(this.container, {node: line, offset: 0});
- select.scrollToCursor(this.container);
- return true;
- },
-
- // Delay (or initiate) the next paren highlight event.
- scheduleParenHighlight: function() {
- if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
- var self = this;
- this.parenEvent = this.parent.setTimeout(function(){self.highlightParens();}, 300);
- },
-
- // Take the token before the cursor. If it contains a character in
- // '()[]{}', search for the matching paren/brace/bracket, and
- // highlight them in green for a moment, or red if no proper match
- // was found.
- highlightParens: function(jump, fromKey) {
- var self = this;
- // give the relevant nodes a colour.
- function highlight(node, ok) {
- if (!node) return;
- if (self.options.markParen) {
- self.options.markParen(node, ok);
- }
- else {
- node.style.fontWeight = "bold";
- node.style.color = ok ? "#8F8" : "#F88";
- }
- }
- function unhighlight(node) {
- if (!node) return;
- if (self.options.unmarkParen) {
- self.options.unmarkParen(node);
- }
- else {
- node.style.fontWeight = "";
- node.style.color = "";
- }
- }
- if (!fromKey && self.highlighted) {
- unhighlight(self.highlighted[0]);
- unhighlight(self.highlighted[1]);
- }
-
- if (!window.select) return;
- // Clear the event property.
- if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
- this.parenEvent = null;
-
- // Extract a 'paren' from a piece of text.
- function paren(node) {
- if (node.currentText) {
- var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/);
- return match && match[1];
- }
- }
- // Determine the direction a paren is facing.
- function forward(ch) {
- return /[\(\[\{]/.test(ch);
- }
-
- var ch, cursor = select.selectionTopNode(this.container, true);
- if (!cursor || !this.highlightAtCursor()) return;
- cursor = select.selectionTopNode(this.container, true);
- if (!(cursor && ((ch = paren(cursor)) || (cursor = cursor.nextSibling) && (ch = paren(cursor)))))
- return;
- // We only look for tokens with the same className.
- var className = cursor.className, dir = forward(ch), match = matching[ch];
-
- // Since parts of the document might not have been properly
- // highlighted, and it is hard to know in advance which part we
- // have to scan, we just try, and when we find dirty nodes we
- // abort, parse them, and re-try.
- function tryFindMatch() {
- var stack = [], ch, ok = true;
- for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) {
- if (runner.className == className && isSpan(runner) && (ch = paren(runner))) {
- if (forward(ch) == dir)
- stack.push(ch);
- else if (!stack.length)
- ok = false;
- else if (stack.pop() != matching[ch])
- ok = false;
- if (!stack.length) break;
- }
- else if (runner.dirty || !isSpan(runner) && !isBR(runner)) {
- return {node: runner, status: "dirty"};
- }
- }
- return {node: runner, status: runner && ok};
- }
-
- while (true) {
- var found = tryFindMatch();
- if (found.status == "dirty") {
- this.highlight(found.node, endOfLine(found.node));
- // Needed because in some corner cases a highlight does not
- // reach a node.
- found.node.dirty = false;
- continue;
- }
- else {
- highlight(cursor, found.status);
- highlight(found.node, found.status);
- if (fromKey)
- self.parent.setTimeout(function() {unhighlight(cursor); unhighlight(found.node);}, 500);
- else
- self.highlighted = [cursor, found.node];
- if (jump && found.node)
- select.focusAfterNode(found.node.previousSibling, this.container);
- break;
- }
- }
- },
-
- // Adjust the amount of whitespace at the start of the line that
- // the cursor is on so that it is indented properly.
- indentAtCursor: function(direction) {
- if (!this.container.firstChild) return;
- // The line has to have up-to-date lexical information, so we
- // highlight it first.
- if (!this.highlightAtCursor()) return;
- var cursor = select.selectionTopNode(this.container, false);
- // If we couldn't determine the place of the cursor,
- // there's nothing to indent.
- if (cursor === false)
- return;
- select.markSelection(this.win);
- this.indentLineAfter(startOfLine(cursor), direction);
- select.selectMarked();
- },
-
- // Indent all lines whose start falls inside of the current
- // selection.
- indentRegion: function(start, end, direction) {
- var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling);
- if (!isBR(end)) end = endOfLine(end, this.container);
- this.addDirtyNode(start);
-
- do {
- var next = endOfLine(current, this.container);
- if (current) this.highlight(before, next, true);
- this.indentLineAfter(current, direction);
- before = current;
- current = next;
- } while (current != end);
- select.setCursorPos(this.container, {node: start, offset: 0}, {node: end, offset: 0});
- },
-
- // Find the node that the cursor is in, mark it as dirty, and make
- // sure a highlight pass is scheduled.
- cursorActivity: function(safe) {
- // pagehide event hack above
- if (this.unloaded) {
- this.win.document.designMode = "off";
- this.win.document.designMode = "on";
- this.unloaded = false;
- }
-
- if (internetExplorer) {
- this.container.createTextRange().execCommand("unlink");
- this.selectionSnapshot = select.getBookmark(this.container);
- }
-
- var activity = this.options.cursorActivity;
- if (!safe || activity) {
- var cursor = select.selectionTopNode(this.container, false);
- if (cursor === false || !this.container.firstChild) return;
- cursor = cursor || this.container.firstChild;
- if (activity) activity(cursor);
- if (!safe) {
- this.scheduleHighlight();
- this.addDirtyNode(cursor);
- }
- }
- },
-
- reparseBuffer: function() {
- forEach(this.container.childNodes, function(node) {node.dirty = true;});
- if (this.container.firstChild)
- this.addDirtyNode(this.container.firstChild);
- },
-
- // Add a node to the set of dirty nodes, if it isn't already in
- // there.
- addDirtyNode: function(node) {
- node = node || this.container.firstChild;
- if (!node) return;
-
- for (var i = 0; i < this.dirty.length; i++)
- if (this.dirty[i] == node) return;
-
- if (node.nodeType != 3)
- node.dirty = true;
- this.dirty.push(node);
- },
-
- allClean: function() {
- return !this.dirty.length;
- },
-
- // Cause a highlight pass to happen in options.passDelay
- // milliseconds. Clear the existing timeout, if one exists. This
- // way, the passes do not happen while the user is typing, and
- // should as unobtrusive as possible.
- scheduleHighlight: function() {
- // Timeouts are routed through the parent window, because on
- // some browsers designMode windows do not fire timeouts.
- var self = this;
- this.parent.clearTimeout(this.highlightTimeout);
- this.highlightTimeout = this.parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay);
- },
-
- // Fetch one dirty node, and remove it from the dirty set.
- getDirtyNode: function() {
- while (this.dirty.length > 0) {
- var found = this.dirty.pop();
- // IE8 sometimes throws an unexplainable 'invalid argument'
- // exception for found.parentNode
- try {
- // If the node has been coloured in the meantime, or is no
- // longer in the document, it should not be returned.
- while (found && found.parentNode != this.container)
- found = found.parentNode;
- if (found && (found.dirty || found.nodeType == 3))
- return found;
- } catch (e) {}
- }
- return null;
- },
-
- // Pick dirty nodes, and highlight them, until options.passTime
- // milliseconds have gone by. The highlight method will continue
- // to next lines as long as it finds dirty nodes. It returns
- // information about the place where it stopped. If there are
- // dirty nodes left after this function has spent all its lines,
- // it shedules another highlight to finish the job.
- highlightDirty: function(force) {
- // Prevent FF from raising an error when it is firing timeouts
- // on a page that's no longer loaded.
- if (!window.select) return false;
-
- if (!this.options.readOnly) select.markSelection(this.win);
- var start, endTime = force ? null : time() + this.options.passTime;
- while ((time() < endTime || force) && (start = this.getDirtyNode())) {
- var result = this.highlight(start, endTime);
- if (result && result.node && result.dirty)
- this.addDirtyNode(result.node);
- }
- if (!this.options.readOnly) select.selectMarked();
- if (start) this.scheduleHighlight();
- return this.dirty.length == 0;
- },
-
- // Creates a function that, when called through a timeout, will
- // continuously re-parse the document.
- documentScanner: function(passTime) {
- var self = this, pos = null;
- return function() {
- // FF timeout weirdness workaround.
- if (!window.select) return;
- // If the current node is no longer in the document... oh
- // well, we start over.
- if (pos && pos.parentNode != self.container)
- pos = null;
- select.markSelection(self.win);
- var result = self.highlight(pos, time() + passTime, true);
- select.selectMarked();
- var newPos = result ? (result.node && result.node.nextSibling) : null;
- pos = (pos == newPos) ? null : newPos;
- self.delayScanning();
- };
- },
-
- // Starts the continuous scanning process for this document after
- // a given interval.
- delayScanning: function() {
- if (this.scanner) {
- this.parent.clearTimeout(this.documentScan);
- this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning);
- }
- },
-
- isIMEOn: function() {
- // chrome: keyDown keyCode is 229 while IME on
- // firefox: no keyUps or keyPresses fires after first keyDown while IME on
- return this.lastKeyDownCode == 229 || this.keyUpOrPressAfterLastKeyDown === false;
- },
-
- // The function that does the actual highlighting/colouring (with
- // help from the parser and the DOM normalizer). Its interface is
- // rather overcomplicated, because it is used in different
- // situations: ensuring that a certain line is highlighted, or
- // highlighting up to X milliseconds starting from a certain
- // point. The 'from' argument gives the node at which it should
- // start. If this is null, it will start at the beginning of the
- // document. When a timestamp is given with the 'target' argument,
- // it will stop highlighting at that time. If this argument holds
- // a DOM node, it will highlight until it reaches that node. If at
- // any time it comes across two 'clean' lines (no dirty nodes), it
- // will stop, except when 'cleanLines' is true. maxBacktrack is
- // the maximum number of lines to backtrack to find an existing
- // parser instance. This is used to give up in situations where a
- // highlight would take too long and freeze the browser interface.
- highlight: function(from, target, cleanLines, maxBacktrack){
- var container = this.container, self = this, active = this.options.activeTokens;
- var endTime = (typeof target == "number" ? target : null);
-
- if (!container.firstChild || this.isIMEOn())
- return false;
- // Backtrack to the first node before from that has a partial
- // parse stored.
- while (from && (!from.parserFromHere || from.dirty)) {
- if (maxBacktrack != null && isBR(from) && (--maxBacktrack) < 0)
- return false;
- from = from.previousSibling;
- }
- // If we are at the end of the document, do nothing.
- if (from && !from.nextSibling)
- return false;
-
- // Check whether a part (<span> node) and the corresponding token
- // match.
- function correctPart(token, part){
- return !part.reduced && part.currentText == token.value && part.className == token.style;
- }
- // Shorten the text associated with a part by chopping off
- // characters from the front. Note that only the currentText
- // property gets changed. For efficiency reasons, we leave the
- // nodeValue alone -- we set the reduced flag to indicate that
- // this part must be replaced.
- function shortenPart(part, minus){
- part.currentText = part.currentText.substring(minus);
- part.reduced = true;
- }
- // Create a part corresponding to a given token.
- function tokenPart(token){
- var part = makePartSpan(token.value, self.doc);
- part.className = token.style;
- return part;
- }
-
- function maybeTouch(node) {
- if (node) {
- var old = node.oldNextSibling;
- if (lineDirty || old === undefined || node.nextSibling != old)
- self.history.touch(node);
- node.oldNextSibling = node.nextSibling;
- }
- else {
- var old = self.container.oldFirstChild;
- if (lineDirty || old === undefined || self.container.firstChild != old)
- self.history.touch(null);
- self.container.oldFirstChild = self.container.firstChild;
- }
- }
-
- // Get the token stream. If from is null, we start with a new
- // parser from the start of the frame, otherwise a partial parse
- // is resumed.
- var traversal = traverseDOM(from ? from.nextSibling : container.firstChild),
- stream = stringStream(traversal),
- parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream);
-
- function surroundedByBRs(node) {
- return (node.previousSibling == null || isBR(node.previousSibling)) &&
- (node.nextSibling == null || isBR(node.nextSibling));
- }
-
- // parts is an interface to make it possible to 'delay' fetching
- // the next DOM node until we are completely done with the one
- // before it. This is necessary because often the next node is
- // not yet available when we want to proceed past the current
- // one.
- var parts = {
- current: null,
- // Fetch current node.
- get: function(){
- if (!this.current)
- this.current = traversal.nodes.shift();
- return this.current;
- },
- // Advance to the next part (do not fetch it yet).
- next: function(){
- this.current = null;
- },
- // Remove the current part from the DOM tree, and move to the
- // next.
- remove: function(){
- container.removeChild(this.get());
- this.current = null;
- },
- // Advance to the next part that is not empty, discarding empty
- // parts.
- getNonEmpty: function(){
- var part = this.get();
- // Allow empty nodes when they are alone on a line, needed
- // for the FF cursor bug workaround (see select.js,
- // insertNewlineAtCursor).
- while (part && isSpan(part) && part.currentText == "") {
- // Leave empty nodes that are alone on a line alone in
- // Opera, since that browsers doesn't deal well with
- // having 2 BRs in a row.
- if (window.opera && surroundedByBRs(part)) {
- this.next();
- part = this.get();
- }
- else {
- var old = part;
- this.remove();
- part = this.get();
- // Adjust selection information, if any. See select.js for details.
- select.snapshotMove(old.firstChild, part && (part.firstChild || part), 0);
- }
- }
-
- return part;
- }
- };
-
- var lineDirty = false, prevLineDirty = true, lineNodes = 0;
-
- // This forEach loops over the tokens from the parsed stream, and
- // at the same time uses the parts object to proceed through the
- // corresponding DOM nodes.
- forEach(parsed, function(token){
- var part = parts.getNonEmpty();
-
- if (token.value == "\n"){
- // The idea of the two streams actually staying synchronized
- // is such a long shot that we explicitly check.
- if (!isBR(part))
- throw "Parser out of sync. Expected BR.";
-
- if (part.dirty || !part.indentation) lineDirty = true;
- maybeTouch(from);
- from = part;
-
- // Every <br> gets a copy of the parser state and a lexical
- // context assigned to it. The first is used to be able to
- // later resume parsing from this point, the second is used
- // for indentation.
- part.parserFromHere = parsed.copy();
- part.indentation = token.indentation;
- part.dirty = false;
-
- // If the target argument wasn't an integer, go at least
- // until that node.
- if (endTime == null && part == target) throw StopIteration;
-
- // A clean line with more than one node means we are done.
- // Throwing a StopIteration is the way to break out of a
- // MochiKit forEach loop.
- if ((endTime != null && time() >= endTime) || (!lineDirty && !prevLineDirty && lineNodes > 1 && !cleanLines))
- throw StopIteration;
- prevLineDirty = lineDirty; lineDirty = false; lineNodes = 0;
- parts.next();
- }
- else {
- if (!isSpan(part))
- throw "Parser out of sync. Expected SPAN.";
- if (part.dirty)
- lineDirty = true;
- lineNodes++;
-
- // If the part matches the token, we can leave it alone.
- if (correctPart(token, part)){
- part.dirty = false;
- parts.next();
- }
- // Otherwise, we have to fix it.
- else {
- lineDirty = true;
- // Insert the correct part.
- var newPart = tokenPart(token);
- container.insertBefore(newPart, part);
- if (active) active(newPart, token, self);
- var tokensize = token.value.length;
- var offset = 0;
- // Eat up parts until the text for this token has been
- // removed, adjusting the stored selection info (see
- // select.js) in the process.
- while (tokensize > 0) {
- part = parts.get();
- var partsize = part.currentText.length;
- select.snapshotReplaceNode(part.firstChild, newPart.firstChild, tokensize, offset);
- if (partsize > tokensize){
- shortenPart(part, tokensize);
- tokensize = 0;
- }
- else {
- tokensize -= partsize;
- offset += partsize;
- parts.remove();
- }
- }
- }
- }
- });
- maybeTouch(from);
-
- // The function returns some status information that is used by
- // hightlightDirty to determine whether and where it has to
- // continue.
- return {node: parts.getNonEmpty(),
- dirty: lineDirty};
- }
- };
-
- return Editor;
-})();
-
-addEventHandler(window, "load", function() {
- var CodeMirror = window.frameElement.CodeMirror;
- var e = CodeMirror.editor = new Editor(CodeMirror.options);
- this.parent.setTimeout(method(CodeMirror, "init"), 0);
-});
+++ /dev/null
-/* This file defines an XML parser, with a few kludges to make it
- * useable for HTML. autoSelfClosers defines a set of tag names that
- * are expected to not have a closing tag, and doNotIndent specifies
- * the tags inside of which no indentation should happen (see Config
- * object). These can be disabled by passing the editor an object like
- * {useHTMLKludges: false} as parserConfig option.
- */
-
-var XMLParser = Editor.Parser = (function() {
- var Kludges = {
- autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
- "meta": true, "col": true, "frame": true, "base": true, "area": true},
- doNotIndent: {"pre": true, "!cdata": true}
- };
- var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}};
- var UseKludges = Kludges;
- var alignCDATA = false;
-
- // Simple stateful tokenizer for XML documents. Returns a
- // MochiKit-style iterator, with a state property that contains a
- // function encapsulating the current state. See tokenize.js.
- var tokenizeXML = (function() {
- function inText(source, setState) {
- var ch = source.next();
- if (ch == "<") {
- if (source.equals("!")) {
- source.next();
- if (source.equals("[")) {
- if (source.lookAhead("[CDATA[", true)) {
- setState(inBlock("xml-cdata", "]]>"));
- return null;
- }
- else {
- return "xml-text";
- }
- }
- else if (source.lookAhead("--", true)) {
- setState(inBlock("xml-comment", "-->"));
- return null;
- }
- else {
- return "xml-text";
- }
- }
- else if (source.equals("?")) {
- source.next();
- source.nextWhileMatches(/[\w\._\-]/);
- setState(inBlock("xml-processing", "?>"));
- return "xml-processing";
- }
- else {
- if (source.equals("/")) source.next();
- setState(inTag);
- return "xml-punctuation";
- }
- }
- else if (ch == "&") {
- while (!source.endOfLine()) {
- if (source.next() == ";")
- break;
- }
- return "xml-entity";
- }
- else {
- source.nextWhileMatches(/[^&<\n]/);
- return "xml-text";
- }
- }
-
- function inTag(source, setState) {
- var ch = source.next();
- if (ch == ">") {
- setState(inText);
- return "xml-punctuation";
- }
- else if (/[?\/]/.test(ch) && source.equals(">")) {
- source.next();
- setState(inText);
- return "xml-punctuation";
- }
- else if (ch == "=") {
- return "xml-punctuation";
- }
- else if (/[\'\"]/.test(ch)) {
- setState(inAttribute(ch));
- return null;
- }
- else {
- source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/);
- return "xml-name";
- }
- }
-
- function inAttribute(quote) {
- return function(source, setState) {
- while (!source.endOfLine()) {
- if (source.next() == quote) {
- setState(inTag);
- break;
- }
- }
- return "xml-attribute";
- };
- }
-
- function inBlock(style, terminator) {
- return function(source, setState) {
- while (!source.endOfLine()) {
- if (source.lookAhead(terminator, true)) {
- setState(inText);
- break;
- }
- source.next();
- }
- return style;
- };
- }
-
- return function(source, startState) {
- return tokenizer(source, startState || inText);
- };
- })();
-
- // The parser. The structure of this function largely follows that of
- // parseJavaScript in parsejavascript.js (there is actually a bit more
- // shared code than I'd like), but it is quite a bit simpler.
- function parseXML(source) {
- var tokens = tokenizeXML(source), token;
- var cc = [base];
- var tokenNr = 0, indented = 0;
- var currentTag = null, context = null;
- var consume;
-
- function push(fs) {
- for (var i = fs.length - 1; i >= 0; i--)
- cc.push(fs[i]);
- }
- function cont() {
- push(arguments);
- consume = true;
- }
- function pass() {
- push(arguments);
- consume = false;
- }
-
- function markErr() {
- token.style += " xml-error";
- }
- function expect(text) {
- return function(style, content) {
- if (content == text) cont();
- else {markErr(); cont(arguments.callee);}
- };
- }
-
- function pushContext(tagname, startOfLine) {
- var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
- context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
- }
- function popContext() {
- context = context.prev;
- }
- function computeIndentation(baseContext) {
- return function(nextChars, current) {
- var context = baseContext;
- if (context && context.noIndent)
- return current;
- if (alignCDATA && /<!\[CDATA\[/.test(nextChars))
- return 0;
- if (context && /^<\//.test(nextChars))
- context = context.prev;
- while (context && !context.startOfLine)
- context = context.prev;
- if (context)
- return context.indent + indentUnit;
- else
- return 0;
- };
- }
-
- function base() {
- return pass(element, base);
- }
- var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, "xml-processing": true};
- function element(style, content) {
- if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
- else if (content == "</") cont(closetagname, expect(">"));
- else if (style == "xml-cdata") {
- if (!context || context.name != "!cdata") pushContext("!cdata");
- if (/\]\]>$/.test(content)) popContext();
- cont();
- }
- else if (harmlessTokens.hasOwnProperty(style)) cont();
- else {markErr(); cont();}
- }
- function tagname(style, content) {
- if (style == "xml-name") {
- currentTag = content.toLowerCase();
- token.style = "xml-tagname";
- cont();
- }
- else {
- currentTag = null;
- pass();
- }
- }
- function closetagname(style, content) {
- if (style == "xml-name") {
- token.style = "xml-tagname";
- if (context && content.toLowerCase() == context.name) popContext();
- else markErr();
- }
- cont();
- }
- function endtag(startOfLine) {
- return function(style, content) {
- if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
- else if (content == ">") {pushContext(currentTag, startOfLine); cont();}
- else {markErr(); cont(arguments.callee);}
- };
- }
- function attributes(style) {
- if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);}
- else pass();
- }
- function attribute(style, content) {
- if (content == "=") cont(value);
- else if (content == ">" || content == "/>") pass(endtag);
- else pass();
- }
- function value(style) {
- if (style == "xml-attribute") cont(value);
- else pass();
- }
-
- return {
- indentation: function() {return indented;},
-
- next: function(){
- token = tokens.next();
- if (token.style == "whitespace" && tokenNr == 0)
- indented = token.value.length;
- else
- tokenNr++;
- if (token.content == "\n") {
- indented = tokenNr = 0;
- token.indentation = computeIndentation(context);
- }
-
- if (token.style == "whitespace" || token.type == "xml-comment")
- return token;
-
- while(true){
- consume = false;
- cc.pop()(token.style, token.content);
- if (consume) return token;
- }
- },
-
- copy: function(){
- var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
- var parser = this;
-
- return function(input){
- cc = _cc.concat([]);
- tokenNr = indented = 0;
- context = _context;
- tokens = tokenizeXML(input, _tokenState);
- return parser;
- };
- }
- };
- }
-
- return {
- make: parseXML,
- electricChars: "/",
- configure: function(config) {
- if (config.useHTMLKludges != null)
- UseKludges = config.useHTMLKludges ? Kludges : NoKludges;
- if (config.alignCDATA)
- alignCDATA = config.alignCDATA;
- }
- };
-})();
+++ /dev/null
-/* Functionality for finding, storing, and restoring selections
- *
- * This does not provide a generic API, just the minimal functionality
- * required by the CodeMirror system.
- */
-
-// Namespace object.
-var select = {};
-
-(function() {
- select.ie_selection = document.selection && document.selection.createRangeCollection;
-
- // Find the 'top-level' (defined as 'a direct child of the node
- // passed as the top argument') node that the given node is
- // contained in. Return null if the given node is not inside the top
- // node.
- function topLevelNodeAt(node, top) {
- while (node && node.parentNode != top)
- node = node.parentNode;
- return node;
- }
-
- // Find the top-level node that contains the node before this one.
- function topLevelNodeBefore(node, top) {
- while (!node.previousSibling && node.parentNode != top)
- node = node.parentNode;
- return topLevelNodeAt(node.previousSibling, top);
- }
-
- var fourSpaces = "\u00a0\u00a0\u00a0\u00a0";
-
- select.scrollToNode = function(node, cursor) {
- if (!node) return;
- var element = node,
- doc = element.ownerDocument, body = doc.body,
- win = (doc.defaultView || doc.parentWindow),
- html = doc.documentElement,
- atEnd = !element.nextSibling || !element.nextSibling.nextSibling
- || !element.nextSibling.nextSibling.nextSibling;
- // In Opera (and recent Webkit versions), BR elements *always*
- // have a offsetTop property of zero.
- var compensateHack = 0;
- while (element && !element.offsetTop) {
- compensateHack++;
- element = element.previousSibling;
- }
- // atEnd is another kludge for these browsers -- if the cursor is
- // at the end of the document, and the node doesn't have an
- // offset, just scroll to the end.
- if (compensateHack == 0) atEnd = false;
-
- // WebKit has a bad habit of (sometimes) happily returning bogus
- // offsets when the document has just been changed. This seems to
- // always be 5/5, so we don't use those.
- if (webkit && element && element.offsetTop == 5 && element.offsetLeft == 5)
- return;
-
- var y = compensateHack * (element ? element.offsetHeight : 0), x = 0,
- width = (node ? node.offsetWidth : 0), pos = element;
- while (pos && pos.offsetParent) {
- y += pos.offsetTop;
- // Don't count X offset for <br> nodes
- if (!isBR(pos))
- x += pos.offsetLeft;
- pos = pos.offsetParent;
- }
-
- var scroll_x = body.scrollLeft || html.scrollLeft || 0,
- scroll_y = body.scrollTop || html.scrollTop || 0,
- scroll = false, screen_width = win.innerWidth || html.clientWidth || 0;
-
- if (cursor || width < screen_width) {
- if (cursor) {
- var off = select.offsetInNode(win, node), size = nodeText(node).length;
- if (size) x += width * (off / size);
- }
- var screen_x = x - scroll_x;
- if (screen_x < 0 || screen_x > screen_width) {
- scroll_x = x;
- scroll = true;
- }
- }
- var screen_y = y - scroll_y;
- if (screen_y < 0 || atEnd || screen_y > (win.innerHeight || html.clientHeight || 0) - 50) {
- scroll_y = atEnd ? 1e6 : y;
- scroll = true;
- }
- if (scroll) win.scrollTo(scroll_x, scroll_y);
- };
-
- select.scrollToCursor = function(container) {
- select.scrollToNode(select.selectionTopNode(container, true) || container.firstChild, true);
- };
-
- // Used to prevent restoring a selection when we do not need to.
- var currentSelection = null;
-
- select.snapshotChanged = function() {
- if (currentSelection) currentSelection.changed = true;
- };
-
- // This is called by the code in editor.js whenever it is replacing
- // a text node. The function sees whether the given oldNode is part
- // of the current selection, and updates this selection if it is.
- // Because nodes are often only partially replaced, the length of
- // the part that gets replaced has to be taken into account -- the
- // selection might stay in the oldNode if the newNode is smaller
- // than the selection's offset. The offset argument is needed in
- // case the selection does move to the new object, and the given
- // length is not the whole length of the new node (part of it might
- // have been used to replace another node).
- select.snapshotReplaceNode = function(from, to, length, offset) {
- if (!currentSelection) return;
-
- function replace(point) {
- if (from == point.node) {
- currentSelection.changed = true;
- if (length && point.offset > length) {
- point.offset -= length;
- }
- else {
- point.node = to;
- point.offset += (offset || 0);
- }
- }
- }
- replace(currentSelection.start);
- replace(currentSelection.end);
- };
-
- select.snapshotMove = function(from, to, distance, relative, ifAtStart) {
- if (!currentSelection) return;
-
- function move(point) {
- if (from == point.node && (!ifAtStart || point.offset == 0)) {
- currentSelection.changed = true;
- point.node = to;
- if (relative) point.offset = Math.max(0, point.offset + distance);
- else point.offset = distance;
- }
- }
- move(currentSelection.start);
- move(currentSelection.end);
- };
-
- // Most functions are defined in two ways, one for the IE selection
- // model, one for the W3C one.
- if (select.ie_selection) {
- function selectionNode(win, start) {
- var range = win.document.selection.createRange();
- range.collapse(start);
-
- function nodeAfter(node) {
- var found = null;
- while (!found && node) {
- found = node.nextSibling;
- node = node.parentNode;
- }
- return nodeAtStartOf(found);
- }
-
- function nodeAtStartOf(node) {
- while (node && node.firstChild) node = node.firstChild;
- return {node: node, offset: 0};
- }
-
- var containing = range.parentElement();
- if (!isAncestor(win.document.body, containing)) return null;
- if (!containing.firstChild) return nodeAtStartOf(containing);
-
- var working = range.duplicate();
- working.moveToElementText(containing);
- working.collapse(true);
- for (var cur = containing.firstChild; cur; cur = cur.nextSibling) {
- if (cur.nodeType == 3) {
- var size = cur.nodeValue.length;
- working.move("character", size);
- }
- else {
- working.moveToElementText(cur);
- working.collapse(false);
- }
-
- var dir = range.compareEndPoints("StartToStart", working);
- if (dir == 0) return nodeAfter(cur);
- if (dir == 1) continue;
- if (cur.nodeType != 3) return nodeAtStartOf(cur);
-
- working.setEndPoint("StartToEnd", range);
- return {node: cur, offset: size - working.text.length};
- }
- return nodeAfter(containing);
- }
-
- select.markSelection = function(win) {
- currentSelection = null;
- var sel = win.document.selection;
- if (!sel) return;
- var start = selectionNode(win, true),
- end = selectionNode(win, false);
- if (!start || !end) return;
- currentSelection = {start: start, end: end, window: win, changed: false};
- };
-
- select.selectMarked = function() {
- if (!currentSelection || !currentSelection.changed) return;
- var win = currentSelection.window, doc = win.document;
-
- function makeRange(point) {
- var range = doc.body.createTextRange(),
- node = point.node;
- if (!node) {
- range.moveToElementText(currentSelection.window.document.body);
- range.collapse(false);
- }
- else if (node.nodeType == 3) {
- range.moveToElementText(node.parentNode);
- var offset = point.offset;
- while (node.previousSibling) {
- node = node.previousSibling;
- offset += (node.innerText || "").length;
- }
- range.move("character", offset);
- }
- else {
- range.moveToElementText(node);
- range.collapse(true);
- }
- return range;
- }
-
- var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end);
- start.setEndPoint("StartToEnd", end);
- start.select();
- };
-
- select.offsetInNode = function(win, node) {
- var sel = win.document.selection;
- if (!sel) return 0;
- var range = sel.createRange(), range2 = range.duplicate();
- try {range2.moveToElementText(node);} catch(e){return 0;}
- range.setEndPoint("StartToStart", range2);
- return range.text.length;
- };
-
- // Get the top-level node that one end of the cursor is inside or
- // after. Note that this returns false for 'no cursor', and null
- // for 'start of document'.
- select.selectionTopNode = function(container, start) {
- var selection = container.ownerDocument.selection;
- if (!selection) return false;
-
- var range = selection.createRange(), range2 = range.duplicate();
- range.collapse(start);
- var around = range.parentElement();
- if (around && isAncestor(container, around)) {
- // Only use this node if the selection is not at its start.
- range2.moveToElementText(around);
- if (range.compareEndPoints("StartToStart", range2) == 1)
- return topLevelNodeAt(around, container);
- }
-
- // Move the start of a range to the start of a node,
- // compensating for the fact that you can't call
- // moveToElementText with text nodes.
- function moveToNodeStart(range, node) {
- if (node.nodeType == 3) {
- var count = 0, cur = node.previousSibling;
- while (cur && cur.nodeType == 3) {
- count += cur.nodeValue.length;
- cur = cur.previousSibling;
- }
- if (cur) {
- try{range.moveToElementText(cur);}
- catch(e){return false;}
- range.collapse(false);
- }
- else range.moveToElementText(node.parentNode);
- if (count) range.move("character", count);
- }
- else {
- try{range.moveToElementText(node);}
- catch(e){return false;}
- }
- return true;
- }
-
- // Do a binary search through the container object, comparing
- // the start of each node to the selection
- var start = 0, end = container.childNodes.length - 1;
- while (start < end) {
- var middle = Math.ceil((end + start) / 2), node = container.childNodes[middle];
- if (!node) return false; // Don't ask. IE6 manages this sometimes.
- if (!moveToNodeStart(range2, node)) return false;
- if (range.compareEndPoints("StartToStart", range2) == 1)
- start = middle;
- else
- end = middle - 1;
- }
- return container.childNodes[start] || null;
- };
-
- // Place the cursor after this.start. This is only useful when
- // manually moving the cursor instead of restoring it to its old
- // position.
- select.focusAfterNode = function(node, container) {
- var range = container.ownerDocument.body.createTextRange();
- range.moveToElementText(node || container);
- range.collapse(!node);
- range.select();
- };
-
- select.somethingSelected = function(win) {
- var sel = win.document.selection;
- return sel && (sel.createRange().text != "");
- };
-
- function insertAtCursor(window, html) {
- var selection = window.document.selection;
- if (selection) {
- var range = selection.createRange();
- range.pasteHTML(html);
- range.collapse(false);
- range.select();
- }
- }
-
- // Used to normalize the effect of the enter key, since browsers
- // do widely different things when pressing enter in designMode.
- select.insertNewlineAtCursor = function(window) {
- insertAtCursor(window, "<br>");
- };
-
- select.insertTabAtCursor = function(window) {
- insertAtCursor(window, fourSpaces);
- };
-
- // Get the BR node at the start of the line on which the cursor
- // currently is, and the offset into the line. Returns null as
- // node if cursor is on first line.
- select.cursorPos = function(container, start) {
- var selection = container.ownerDocument.selection;
- if (!selection) return null;
-
- var topNode = select.selectionTopNode(container, start);
- while (topNode && !isBR(topNode))
- topNode = topNode.previousSibling;
-
- var range = selection.createRange(), range2 = range.duplicate();
- range.collapse(start);
- if (topNode) {
- range2.moveToElementText(topNode);
- range2.collapse(false);
- }
- else {
- // When nothing is selected, we can get all kinds of funky errors here.
- try { range2.moveToElementText(container); }
- catch (e) { return null; }
- range2.collapse(true);
- }
- range.setEndPoint("StartToStart", range2);
-
- return {node: topNode, offset: range.text.length};
- };
-
- select.setCursorPos = function(container, from, to) {
- function rangeAt(pos) {
- var range = container.ownerDocument.body.createTextRange();
- if (!pos.node) {
- range.moveToElementText(container);
- range.collapse(true);
- }
- else {
- range.moveToElementText(pos.node);
- range.collapse(false);
- }
- range.move("character", pos.offset);
- return range;
- }
-
- var range = rangeAt(from);
- if (to && to != from)
- range.setEndPoint("EndToEnd", rangeAt(to));
- range.select();
- }
-
- // Some hacks for storing and re-storing the selection when the editor loses and regains focus.
- select.getBookmark = function (container) {
- var from = select.cursorPos(container, true), to = select.cursorPos(container, false);
- if (from && to) return {from: from, to: to};
- };
-
- // Restore a stored selection.
- select.setBookmark = function(container, mark) {
- if (!mark) return;
- select.setCursorPos(container, mark.from, mark.to);
- };
- }
- // W3C model
- else {
- // Find the node right at the cursor, not one of its
- // ancestors with a suitable offset. This goes down the DOM tree
- // until a 'leaf' is reached (or is it *up* the DOM tree?).
- function innerNode(node, offset) {
- while (node.nodeType != 3 && !isBR(node)) {
- var newNode = node.childNodes[offset] || node.nextSibling;
- offset = 0;
- while (!newNode && node.parentNode) {
- node = node.parentNode;
- newNode = node.nextSibling;
- }
- node = newNode;
- if (!newNode) break;
- }
- return {node: node, offset: offset};
- }
-
- // Store start and end nodes, and offsets within these, and refer
- // back to the selection object from those nodes, so that this
- // object can be updated when the nodes are replaced before the
- // selection is restored.
- select.markSelection = function (win) {
- var selection = win.getSelection();
- if (!selection || selection.rangeCount == 0)
- return (currentSelection = null);
- var range = selection.getRangeAt(0);
-
- currentSelection = {
- start: innerNode(range.startContainer, range.startOffset),
- end: innerNode(range.endContainer, range.endOffset),
- window: win,
- changed: false
- };
- };
-
- select.selectMarked = function () {
- var cs = currentSelection;
- // on webkit-based browsers, it is apparently possible that the
- // selection gets reset even when a node that is not one of the
- // endpoints get messed with. the most common situation where
- // this occurs is when a selection is deleted or overwitten. we
- // check for that here.
- function focusIssue() {
- if (cs.start.node == cs.end.node && cs.start.offset == cs.end.offset) {
- var selection = cs.window.getSelection();
- if (!selection || selection.rangeCount == 0) return true;
- var range = selection.getRangeAt(0), point = innerNode(range.startContainer, range.startOffset);
- return cs.start.node != point.node || cs.start.offset != point.offset;
- }
- }
- if (!cs || !(cs.changed || (webkit && focusIssue()))) return;
- var win = cs.window, range = win.document.createRange();
-
- function setPoint(point, which) {
- if (point.node) {
- // Some magic to generalize the setting of the start and end
- // of a range.
- if (point.offset == 0)
- range["set" + which + "Before"](point.node);
- else
- range["set" + which](point.node, point.offset);
- }
- else {
- range.setStartAfter(win.document.body.lastChild || win.document.body);
- }
- }
-
- setPoint(cs.end, "End");
- setPoint(cs.start, "Start");
- selectRange(range, win);
- };
-
- // Helper for selecting a range object.
- function selectRange(range, window) {
- var selection = window.getSelection();
- if (!selection) return;
- selection.removeAllRanges();
- selection.addRange(range);
- }
- function selectionRange(window) {
- var selection = window.getSelection();
- if (!selection || selection.rangeCount == 0)
- return false;
- else
- return selection.getRangeAt(0);
- }
-
- // Finding the top-level node at the cursor in the W3C is, as you
- // can see, quite an involved process.
- select.selectionTopNode = function(container, start) {
- var range = selectionRange(container.ownerDocument.defaultView);
- if (!range) return false;
-
- var node = start ? range.startContainer : range.endContainer;
- var offset = start ? range.startOffset : range.endOffset;
- // Work around (yet another) bug in Opera's selection model.
- if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
- container.childNodes[range.startOffset] && isBR(container.childNodes[range.startOffset]))
- offset--;
-
- // For text nodes, we look at the node itself if the cursor is
- // inside, or at the node before it if the cursor is at the
- // start.
- if (node.nodeType == 3){
- if (offset > 0)
- return topLevelNodeAt(node, container);
- else
- return topLevelNodeBefore(node, container);
- }
- // Occasionally, browsers will return the HTML node as
- // selection. If the offset is 0, we take the start of the frame
- // ('after null'), otherwise, we take the last node.
- else if (node.nodeName.toUpperCase() == "HTML") {
- return (offset == 1 ? null : container.lastChild);
- }
- // If the given node is our 'container', we just look up the
- // correct node by using the offset.
- else if (node == container) {
- return (offset == 0) ? null : node.childNodes[offset - 1];
- }
- // In any other case, we have a regular node. If the cursor is
- // at the end of the node, we use the node itself, if it is at
- // the start, we use the node before it, and in any other
- // case, we look up the child before the cursor and use that.
- else {
- if (offset == node.childNodes.length)
- return topLevelNodeAt(node, container);
- else if (offset == 0)
- return topLevelNodeBefore(node, container);
- else
- return topLevelNodeAt(node.childNodes[offset - 1], container);
- }
- };
-
- select.focusAfterNode = function(node, container) {
- var win = container.ownerDocument.defaultView,
- range = win.document.createRange();
- range.setStartBefore(container.firstChild || container);
- // In Opera, setting the end of a range at the end of a line
- // (before a BR) will cause the cursor to appear on the next
- // line, so we set the end inside of the start node when
- // possible.
- if (node && !node.firstChild)
- range.setEndAfter(node);
- else if (node)
- range.setEnd(node, node.childNodes.length);
- else
- range.setEndBefore(container.firstChild || container);
- range.collapse(false);
- selectRange(range, win);
- };
-
- select.somethingSelected = function(win) {
- var range = selectionRange(win);
- return range && !range.collapsed;
- };
-
- select.offsetInNode = function(win, node) {
- var range = selectionRange(win);
- if (!range) return 0;
- range = range.cloneRange();
- range.setStartBefore(node);
- return range.toString().length;
- };
-
- function insertNodeAtCursor(window, node) {
- var range = selectionRange(window);
- if (!range) return;
-
- range.deleteContents();
- range.insertNode(node);
-
- // work around weirdness where Opera will magically insert a new
- // BR node when a BR node inside a span is moved around. makes
- // sure the BR ends up outside of spans.
- if (window.opera && isBR(node) && isSpan(node.parentNode)) {
- var next = node.nextSibling, p = node.parentNode, outer = p.parentNode;
- outer.insertBefore(node, p.nextSibling);
- var textAfter = "";
- for (; next && next.nodeType == 3; next = next.nextSibling) {
- textAfter += next.nodeValue;
- removeElement(next);
- }
- outer.insertBefore(makePartSpan(textAfter, window.document), node.nextSibling);
- }
- range = window.document.createRange();
- range.selectNode(node);
- range.collapse(false);
- selectRange(range, window);
- }
-
- select.insertNewlineAtCursor = function(window) {
- if (webkit)
- document.execCommand('insertLineBreak');
- else
- insertNodeAtCursor(window, window.document.createElement("BR"));
- };
-
- select.insertTabAtCursor = function(window) {
- insertNodeAtCursor(window, window.document.createTextNode(fourSpaces));
- };
-
- select.cursorPos = function(container, start) {
- var range = selectionRange(window);
- if (!range) return;
-
- var topNode = select.selectionTopNode(container, start);
- while (topNode && !isBR(topNode))
- topNode = topNode.previousSibling;
-
- range = range.cloneRange();
- range.collapse(start);
- if (topNode)
- range.setStartAfter(topNode);
- else
- range.setStartBefore(container);
-
- return {node: topNode, offset: range.toString().length};
- };
-
- select.setCursorPos = function(container, from, to) {
- var win = container.ownerDocument.defaultView,
- range = win.document.createRange();
-
- function setPoint(node, offset, side) {
- if (offset == 0 && node && !node.nextSibling) {
- range["set" + side + "After"](node);
- return true;
- }
-
- if (!node)
- node = container.firstChild;
- else
- node = node.nextSibling;
-
- if (!node) return;
-
- if (offset == 0) {
- range["set" + side + "Before"](node);
- return true;
- }
-
- var backlog = []
- function decompose(node) {
- if (node.nodeType == 3)
- backlog.push(node);
- else
- forEach(node.childNodes, decompose);
- }
- while (true) {
- while (node && !backlog.length) {
- decompose(node);
- node = node.nextSibling;
- }
- var cur = backlog.shift();
- if (!cur) return false;
-
- var length = cur.nodeValue.length;
- if (length >= offset) {
- range["set" + side](cur, offset);
- return true;
- }
- offset -= length;
- }
- }
-
- to = to || from;
- if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
- selectRange(range, win);
- };
- }
-})();
+++ /dev/null
-/* String streams are the things fed to parsers (which can feed them
- * to a tokenizer if they want). They provide peek and next methods
- * for looking at the current character (next 'consumes' this
- * character, peek does not), and a get method for retrieving all the
- * text that was consumed since the last time get was called.
- *
- * An easy mistake to make is to let a StopIteration exception finish
- * the token stream while there are still characters pending in the
- * string stream (hitting the end of the buffer while parsing a
- * token). To make it easier to detect such errors, the stringstreams
- * throw an exception when this happens.
- */
-
-// Make a stringstream stream out of an iterator that returns strings.
-// This is applied to the result of traverseDOM (see codemirror.js),
-// and the resulting stream is fed to the parser.
-var stringStream = function(source){
- // String that's currently being iterated over.
- var current = "";
- // Position in that string.
- var pos = 0;
- // Accumulator for strings that have been iterated over but not
- // get()-ed yet.
- var accum = "";
- // Make sure there are more characters ready, or throw
- // StopIteration.
- function ensureChars() {
- while (pos == current.length) {
- accum += current;
- current = ""; // In case source.next() throws
- pos = 0;
- try {current = source.next();}
- catch (e) {
- if (e != StopIteration) throw e;
- else return false;
- }
- }
- return true;
- }
-
- return {
- // Return the next character in the stream.
- peek: function() {
- if (!ensureChars()) return null;
- return current.charAt(pos);
- },
- // Get the next character, throw StopIteration if at end, check
- // for unused content.
- next: function() {
- if (!ensureChars()) {
- if (accum.length > 0)
- throw "End of stringstream reached without emptying buffer ('" + accum + "').";
- else
- throw StopIteration;
- }
- return current.charAt(pos++);
- },
- // Return the characters iterated over since the last call to
- // .get().
- get: function() {
- var temp = accum;
- accum = "";
- if (pos > 0){
- temp += current.slice(0, pos);
- current = current.slice(pos);
- pos = 0;
- }
- return temp;
- },
- // Push a string back into the stream.
- push: function(str) {
- current = current.slice(0, pos) + str + current.slice(pos);
- },
- lookAhead: function(str, consume, skipSpaces, caseInsensitive) {
- function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
- str = cased(str);
- var found = false;
-
- var _accum = accum, _pos = pos;
- if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/);
-
- while (true) {
- var end = pos + str.length, left = current.length - pos;
- if (end <= current.length) {
- found = str == cased(current.slice(pos, end));
- pos = end;
- break;
- }
- else if (str.slice(0, left) == cased(current.slice(pos))) {
- accum += current; current = "";
- try {current = source.next();}
- catch (e) {break;}
- pos = 0;
- str = str.slice(left);
- }
- else {
- break;
- }
- }
-
- if (!(found && consume)) {
- current = accum.slice(_accum.length) + current;
- pos = _pos;
- accum = _accum;
- }
-
- return found;
- },
-
- // Utils built on top of the above
- more: function() {
- return this.peek() !== null;
- },
- applies: function(test) {
- var next = this.peek();
- return (next !== null && test(next));
- },
- nextWhile: function(test) {
- var next;
- while ((next = this.peek()) !== null && test(next))
- this.next();
- },
- matches: function(re) {
- var next = this.peek();
- return (next !== null && re.test(next));
- },
- nextWhileMatches: function(re) {
- var next;
- while ((next = this.peek()) !== null && re.test(next))
- this.next();
- },
- equals: function(ch) {
- return ch === this.peek();
- },
- endOfLine: function() {
- var next = this.peek();
- return next == null || next == "\n";
- }
- };
-};
+++ /dev/null
-// A framework for simple tokenizers. Takes care of newlines and
-// white-space, and of getting the text from the source stream into
-// the token object. A state is a function of two arguments -- a
-// string stream and a setState function. The second can be used to
-// change the tokenizer's state, and can be ignored for stateless
-// tokenizers. This function should advance the stream over a token
-// and return a string or object containing information about the next
-// token, or null to pass and have the (new) state be called to finish
-// the token. When a string is given, it is wrapped in a {style, type}
-// object. In the resulting object, the characters consumed are stored
-// under the content property. Any whitespace following them is also
-// automatically consumed, and added to the value property. (Thus,
-// content is the actual meaningful part of the token, while value
-// contains all the text it spans.)
-
-function tokenizer(source, state) {
- // Newlines are always a separate token.
- function isWhiteSpace(ch) {
- // The messy regexp is because IE's regexp matcher is of the
- // opinion that non-breaking spaces are no whitespace.
- return ch != "\n" && /^[\s\u00a0]*$/.test(ch);
- }
-
- var tokenizer = {
- state: state,
-
- take: function(type) {
- if (typeof(type) == "string")
- type = {style: type, type: type};
-
- type.content = (type.content || "") + source.get();
- if (!/\n$/.test(type.content))
- source.nextWhile(isWhiteSpace);
- type.value = type.content + source.get();
- return type;
- },
-
- next: function () {
- if (!source.more()) throw StopIteration;
-
- var type;
- if (source.equals("\n")) {
- source.next();
- return this.take("whitespace");
- }
-
- if (source.applies(isWhiteSpace))
- type = "whitespace";
- else
- while (!type)
- type = this.state(source, function(s) {tokenizer.state = s;});
-
- return this.take(type);
- }
- };
- return tokenizer;
-}
+++ /dev/null
-/**
- * Storage and control for undo information within a CodeMirror
- * editor. 'Why on earth is such a complicated mess required for
- * that?', I hear you ask. The goal, in implementing this, was to make
- * the complexity of storing and reverting undo information depend
- * only on the size of the edited or restored content, not on the size
- * of the whole document. This makes it necessary to use a kind of
- * 'diff' system, which, when applied to a DOM tree, causes some
- * complexity and hackery.
- *
- * In short, the editor 'touches' BR elements as it parses them, and
- * the UndoHistory stores these. When nothing is touched in commitDelay
- * milliseconds, the changes are committed: It goes over all touched
- * nodes, throws out the ones that did not change since last commit or
- * are no longer in the document, and assembles the rest into zero or
- * more 'chains' -- arrays of adjacent lines. Links back to these
- * chains are added to the BR nodes, while the chain that previously
- * spanned these nodes is added to the undo history. Undoing a change
- * means taking such a chain off the undo history, restoring its
- * content (text is saved per line) and linking it back into the
- * document.
- */
-
-// A history object needs to know about the DOM container holding the
-// document, the maximum amount of undo levels it should store, the
-// delay (of no input) after which it commits a set of changes, and,
-// unfortunately, the 'parent' window -- a window that is not in
-// designMode, and on which setTimeout works in every browser.
-function UndoHistory(container, maxDepth, commitDelay, editor) {
- this.container = container;
- this.maxDepth = maxDepth; this.commitDelay = commitDelay;
- this.editor = editor; this.parent = editor.parent;
- // This line object represents the initial, empty editor.
- var initial = {text: "", from: null, to: null};
- // As the borders between lines are represented by BR elements, the
- // start of the first line and the end of the last one are
- // represented by null. Since you can not store any properties
- // (links to line objects) in null, these properties are used in
- // those cases.
- this.first = initial; this.last = initial;
- // Similarly, a 'historyTouched' property is added to the BR in
- // front of lines that have already been touched, and 'firstTouched'
- // is used for the first line.
- this.firstTouched = false;
- // History is the set of committed changes, touched is the set of
- // nodes touched since the last commit.
- this.history = []; this.redoHistory = []; this.touched = [];
-}
-
-UndoHistory.prototype = {
- // Schedule a commit (if no other touches come in for commitDelay
- // milliseconds).
- scheduleCommit: function() {
- var self = this;
- this.parent.clearTimeout(this.commitTimeout);
- this.commitTimeout = this.parent.setTimeout(function(){self.tryCommit();}, this.commitDelay);
- },
-
- // Mark a node as touched. Null is a valid argument.
- touch: function(node) {
- this.setTouched(node);
- this.scheduleCommit();
- },
-
- // Undo the last change.
- undo: function() {
- // Make sure pending changes have been committed.
- this.commit();
-
- if (this.history.length) {
- // Take the top diff from the history, apply it, and store its
- // shadow in the redo history.
- var item = this.history.pop();
- this.redoHistory.push(this.updateTo(item, "applyChain"));
- this.notifyEnvironment();
- return this.chainNode(item);
- }
- },
-
- // Redo the last undone change.
- redo: function() {
- this.commit();
- if (this.redoHistory.length) {
- // The inverse of undo, basically.
- var item = this.redoHistory.pop();
- this.addUndoLevel(this.updateTo(item, "applyChain"));
- this.notifyEnvironment();
- return this.chainNode(item);
- }
- },
-
- clear: function() {
- this.history = [];
- this.redoHistory = [];
- },
-
- // Ask for the size of the un/redo histories.
- historySize: function() {
- return {undo: this.history.length, redo: this.redoHistory.length};
- },
-
- // Push a changeset into the document.
- push: function(from, to, lines) {
- var chain = [];
- for (var i = 0; i < lines.length; i++) {
- var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR");
- chain.push({from: from, to: end, text: cleanText(lines[i])});
- from = end;
- }
- this.pushChains([chain], from == null && to == null);
- this.notifyEnvironment();
- },
-
- pushChains: function(chains, doNotHighlight) {
- this.commit(doNotHighlight);
- this.addUndoLevel(this.updateTo(chains, "applyChain"));
- this.redoHistory = [];
- },
-
- // Retrieve a DOM node from a chain (for scrolling to it after undo/redo).
- chainNode: function(chains) {
- for (var i = 0; i < chains.length; i++) {
- var start = chains[i][0], node = start && (start.from || start.to);
- if (node) return node;
- }
- },
-
- // Clear the undo history, make the current document the start
- // position.
- reset: function() {
- this.history = []; this.redoHistory = [];
- },
-
- textAfter: function(br) {
- return this.after(br).text;
- },
-
- nodeAfter: function(br) {
- return this.after(br).to;
- },
-
- nodeBefore: function(br) {
- return this.before(br).from;
- },
-
- // Commit unless there are pending dirty nodes.
- tryCommit: function() {
- if (!window.UndoHistory) return; // Stop when frame has been unloaded
- if (this.editor.highlightDirty()) this.commit(true);
- else this.scheduleCommit();
- },
-
- // Check whether the touched nodes hold any changes, if so, commit
- // them.
- commit: function(doNotHighlight) {
- this.parent.clearTimeout(this.commitTimeout);
- // Make sure there are no pending dirty nodes.
- if (!doNotHighlight) this.editor.highlightDirty(true);
- // Build set of chains.
- var chains = this.touchedChains(), self = this;
-
- if (chains.length) {
- this.addUndoLevel(this.updateTo(chains, "linkChain"));
- this.redoHistory = [];
- this.notifyEnvironment();
- }
- },
-
- // [ end of public interface ]
-
- // Update the document with a given set of chains, return its
- // shadow. updateFunc should be "applyChain" or "linkChain". In the
- // second case, the chains are taken to correspond the the current
- // document, and only the state of the line data is updated. In the
- // first case, the content of the chains is also pushed iinto the
- // document.
- updateTo: function(chains, updateFunc) {
- var shadows = [], dirty = [];
- for (var i = 0; i < chains.length; i++) {
- shadows.push(this.shadowChain(chains[i]));
- dirty.push(this[updateFunc](chains[i]));
- }
- if (updateFunc == "applyChain")
- this.notifyDirty(dirty);
- return shadows;
- },
-
- // Notify the editor that some nodes have changed.
- notifyDirty: function(nodes) {
- forEach(nodes, method(this.editor, "addDirtyNode"))
- this.editor.scheduleHighlight();
- },
-
- notifyEnvironment: function() {
- if (this.onChange) this.onChange();
- // Used by the line-wrapping line-numbering code.
- if (window.frameElement && window.frameElement.CodeMirror.updateNumbers)
- window.frameElement.CodeMirror.updateNumbers();
- },
-
- // Link a chain into the DOM nodes (or the first/last links for null
- // nodes).
- linkChain: function(chain) {
- for (var i = 0; i < chain.length; i++) {
- var line = chain[i];
- if (line.from) line.from.historyAfter = line;
- else this.first = line;
- if (line.to) line.to.historyBefore = line;
- else this.last = line;
- }
- },
-
- // Get the line object after/before a given node.
- after: function(node) {
- return node ? node.historyAfter : this.first;
- },
- before: function(node) {
- return node ? node.historyBefore : this.last;
- },
-
- // Mark a node as touched if it has not already been marked.
- setTouched: function(node) {
- if (node) {
- if (!node.historyTouched) {
- this.touched.push(node);
- node.historyTouched = true;
- }
- }
- else {
- this.firstTouched = true;
- }
- },
-
- // Store a new set of undo info, throw away info if there is more of
- // it than allowed.
- addUndoLevel: function(diffs) {
- this.history.push(diffs);
- if (this.history.length > this.maxDepth)
- this.history.shift();
- },
-
- // Build chains from a set of touched nodes.
- touchedChains: function() {
- var self = this;
-
- // The temp system is a crummy hack to speed up determining
- // whether a (currently touched) node has a line object associated
- // with it. nullTemp is used to store the object for the first
- // line, other nodes get it stored in their historyTemp property.
- var nullTemp = null;
- function temp(node) {return node ? node.historyTemp : nullTemp;}
- function setTemp(node, line) {
- if (node) node.historyTemp = line;
- else nullTemp = line;
- }
-
- function buildLine(node) {
- var text = [];
- for (var cur = node ? node.nextSibling : self.container.firstChild;
- cur && !isBR(cur); cur = cur.nextSibling)
- if (cur.currentText) text.push(cur.currentText);
- return {from: node, to: cur, text: cleanText(text.join(""))};
- }
-
- // Filter out unchanged lines and nodes that are no longer in the
- // document. Build up line objects for remaining nodes.
- var lines = [];
- if (self.firstTouched) self.touched.push(null);
- forEach(self.touched, function(node) {
- if (node && node.parentNode != self.container) return;
-
- if (node) node.historyTouched = false;
- else self.firstTouched = false;
-
- var line = buildLine(node), shadow = self.after(node);
- if (!shadow || shadow.text != line.text || shadow.to != line.to) {
- lines.push(line);
- setTemp(node, line);
- }
- });
-
- // Get the BR element after/before the given node.
- function nextBR(node, dir) {
- var link = dir + "Sibling", search = node[link];
- while (search && !isBR(search))
- search = search[link];
- return search;
- }
-
- // Assemble line objects into chains by scanning the DOM tree
- // around them.
- var chains = []; self.touched = [];
- forEach(lines, function(line) {
- // Note that this makes the loop skip line objects that have
- // been pulled into chains by lines before them.
- if (!temp(line.from)) return;
-
- var chain = [], curNode = line.from, safe = true;
- // Put any line objects (referred to by temp info) before this
- // one on the front of the array.
- while (true) {
- var curLine = temp(curNode);
- if (!curLine) {
- if (safe) break;
- else curLine = buildLine(curNode);
- }
- chain.unshift(curLine);
- setTemp(curNode, null);
- if (!curNode) break;
- safe = self.after(curNode);
- curNode = nextBR(curNode, "previous");
- }
- curNode = line.to; safe = self.before(line.from);
- // Add lines after this one at end of array.
- while (true) {
- if (!curNode) break;
- var curLine = temp(curNode);
- if (!curLine) {
- if (safe) break;
- else curLine = buildLine(curNode);
- }
- chain.push(curLine);
- setTemp(curNode, null);
- safe = self.before(curNode);
- curNode = nextBR(curNode, "next");
- }
- chains.push(chain);
- });
-
- return chains;
- },
-
- // Find the 'shadow' of a given chain by following the links in the
- // DOM nodes at its start and end.
- shadowChain: function(chain) {
- var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to;
- while (true) {
- shadows.push(next);
- var nextNode = next.to;
- if (!nextNode || nextNode == end)
- break;
- else
- next = nextNode.historyAfter || this.before(end);
- // (The this.before(end) is a hack -- FF sometimes removes
- // properties from BR nodes, in which case the best we can hope
- // for is to not break.)
- }
- return shadows;
- },
-
- // Update the DOM tree to contain the lines specified in a given
- // chain, link this chain into the DOM nodes.
- applyChain: function(chain) {
- // Some attempt is made to prevent the cursor from jumping
- // randomly when an undo or redo happens. It still behaves a bit
- // strange sometimes.
- var cursor = select.cursorPos(this.container, false), self = this;
-
- // Remove all nodes in the DOM tree between from and to (null for
- // start/end of container).
- function removeRange(from, to) {
- var pos = from ? from.nextSibling : self.container.firstChild;
- while (pos != to) {
- var temp = pos.nextSibling;
- removeElement(pos);
- pos = temp;
- }
- }
-
- var start = chain[0].from, end = chain[chain.length - 1].to;
- // Clear the space where this change has to be made.
- removeRange(start, end);
-
- // Insert the content specified by the chain into the DOM tree.
- for (var i = 0; i < chain.length; i++) {
- var line = chain[i];
- // The start and end of the space are already correct, but BR
- // tags inside it have to be put back.
- if (i > 0)
- self.container.insertBefore(line.from, end);
-
- // Add the text.
- var node = makePartSpan(fixSpaces(line.text), this.container.ownerDocument);
- self.container.insertBefore(node, end);
- // See if the cursor was on this line. Put it back, adjusting
- // for changed line length, if it was.
- if (cursor && cursor.node == line.from) {
- var cursordiff = 0;
- var prev = this.after(line.from);
- if (prev && i == chain.length - 1) {
- // Only adjust if the cursor is after the unchanged part of
- // the line.
- for (var match = 0; match < cursor.offset &&
- line.text.charAt(match) == prev.text.charAt(match); match++);
- if (cursor.offset > match)
- cursordiff = line.text.length - prev.text.length;
- }
- select.setCursorPos(this.container, {node: line.from, offset: Math.max(0, cursor.offset + cursordiff)});
- }
- // Cursor was in removed line, this is last new line.
- else if (cursor && (i == chain.length - 1) && cursor.node && cursor.node.parentNode != this.container) {
- select.setCursorPos(this.container, {node: line.from, offset: line.text.length});
- }
- }
-
- // Anchor the chain in the DOM tree.
- this.linkChain(chain);
- return start;
- }
-};
+++ /dev/null
-/* A few useful utility functions. */
-
-// Capture a method on an object.
-function method(obj, name) {
- return function() {obj[name].apply(obj, arguments);};
-}
-
-// The value used to signal the end of a sequence in iterators.
-var StopIteration = {toString: function() {return "StopIteration"}};
-
-// Apply a function to each element in a sequence.
-function forEach(iter, f) {
- if (iter.next) {
- try {while (true) f(iter.next());}
- catch (e) {if (e != StopIteration) throw e;}
- }
- else {
- for (var i = 0; i < iter.length; i++)
- f(iter[i]);
- }
-}
-
-// Map a function over a sequence, producing an array of results.
-function map(iter, f) {
- var accum = [];
- forEach(iter, function(val) {accum.push(f(val));});
- return accum;
-}
-
-// Create a predicate function that tests a string againsts a given
-// regular expression. No longer used but might be used by 3rd party
-// parsers.
-function matcher(regexp){
- return function(value){return regexp.test(value);};
-}
-
-// Test whether a DOM node has a certain CSS class. Much faster than
-// the MochiKit equivalent, for some reason.
-function hasClass(element, className){
- var classes = element.className;
- return classes && new RegExp("(^| )" + className + "($| )").test(classes);
-}
-
-// Insert a DOM node after another node.
-function insertAfter(newNode, oldNode) {
- var parent = oldNode.parentNode;
- parent.insertBefore(newNode, oldNode.nextSibling);
- return newNode;
-}
-
-function removeElement(node) {
- if (node.parentNode)
- node.parentNode.removeChild(node);
-}
-
-function clearElement(node) {
- while (node.firstChild)
- node.removeChild(node.firstChild);
-}
-
-// Check whether a node is contained in another one.
-function isAncestor(node, child) {
- while (child = child.parentNode) {
- if (node == child)
- return true;
- }
- return false;
-}
-
-// The non-breaking space character.
-var nbsp = "\u00a0";
-var matching = {"{": "}", "[": "]", "(": ")",
- "}": "{", "]": "[", ")": "("};
-
-// Standardize a few unportable event properties.
-function normalizeEvent(event) {
- if (!event.stopPropagation) {
- event.stopPropagation = function() {this.cancelBubble = true;};
- event.preventDefault = function() {this.returnValue = false;};
- }
- if (!event.stop) {
- event.stop = function() {
- this.stopPropagation();
- this.preventDefault();
- };
- }
-
- if (event.type == "keypress") {
- event.code = (event.charCode == null) ? event.keyCode : event.charCode;
- event.character = String.fromCharCode(event.code);
- }
- return event;
-}
-
-// Portably register event handlers.
-function addEventHandler(node, type, handler, removeFunc) {
- function wrapHandler(event) {
- handler(normalizeEvent(event || window.event));
- }
- if (typeof node.addEventListener == "function") {
- node.addEventListener(type, wrapHandler, false);
- if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);};
- }
- else {
- node.attachEvent("on" + type, wrapHandler);
- if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);};
- }
-}
-
-function nodeText(node) {
- return node.textContent || node.innerText || node.nodeValue || "";
-}
-
-function nodeTop(node) {
- var top = 0;
- while (node.offsetParent) {
- top += node.offsetTop;
- node = node.offsetParent;
- }
- return top;
-}
-
-function isBR(node) {
- var nn = node.nodeName;
- return nn == "BR" || nn == "br";
-}
-function isSpan(node) {
- var nn = node.nodeName;
- return nn == "SPAN" || nn == "span";
-}
--- /dev/null
+/* BASICS */
+
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px;
+ color: black;
+ direction: ltr;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre.CodeMirror-line,
+.CodeMirror pre.CodeMirror-line-like {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+ white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+ border-left: 1px solid black;
+ border-right: none;
+ width: 0;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.cm-fat-cursor .CodeMirror-cursor {
+ width: auto;
+ border: 0 !important;
+ background: #7e7;
+}
+.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+.cm-fat-cursor-mark {
+ background-color: rgba(20, 255, 20, 0.5);
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+}
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+@-moz-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@-webkit-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror-overwrite .CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-rulers {
+ position: absolute;
+ left: 0; right: 0; top: -50px; bottom: 0;
+ overflow: hidden;
+}
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ top: 0; bottom: 0;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+ background: white;
+}
+
+.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actual scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ min-height: 100%;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ display: inline-block;
+ vertical-align: top;
+ margin-bottom: -30px;
+}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
+.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
+
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre.CodeMirror-line,
+.CodeMirror pre.CodeMirror-line-like {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: contextual;
+ font-variant-ligatures: contextual;
+}
+.CodeMirror-wrap pre.CodeMirror-line,
+.CodeMirror-wrap pre.CodeMirror-line-like {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ padding: 0.1px; /* Force widget margins to stay inside of the container */
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-rtl pre { direction: rtl; }
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+}
+
+.CodeMirror-cursor {
+ position: absolute;
+ pointer-events: none;
+}
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
+}
+div.CodeMirror-dragcursors {
+ visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+ background-color: #ffa;
+ background-color: rgba(255, 255, 0, .4);
+}
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursors {
+ visibility: hidden;
+ }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
--- /dev/null
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+// This is CodeMirror (https://codemirror.net), a code editor
+// implemented in JavaScript on top of the browser's DOM.
+//
+// You can find some technical background for some of the code below
+// at http://marijnhaverbeke.nl/blog/#cm-internals .
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.CodeMirror = factory());
+}(this, (function () { 'use strict';
+
+ // Kludges for bugs and behavior differences that can't be feature
+ // detected are enabled based on userAgent etc sniffing.
+ var userAgent = navigator.userAgent;
+ var platform = navigator.platform;
+
+ var gecko = /gecko\/\d/i.test(userAgent);
+ var ie_upto10 = /MSIE \d/.test(userAgent);
+ var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent);
+ var edge = /Edge\/(\d+)/.exec(userAgent);
+ var ie = ie_upto10 || ie_11up || edge;
+ var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]);
+ var webkit = !edge && /WebKit\//.test(userAgent);
+ var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent);
+ var chrome = !edge && /Chrome\//.test(userAgent);
+ var presto = /Opera\//.test(userAgent);
+ var safari = /Apple Computer/.test(navigator.vendor);
+ var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent);
+ var phantom = /PhantomJS/.test(userAgent);
+
+ var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent);
+ var android = /Android/.test(userAgent);
+ // This is woefully incomplete. Suggestions for alternative methods welcome.
+ var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);
+ var mac = ios || /Mac/.test(platform);
+ var chromeOS = /\bCrOS\b/.test(userAgent);
+ var windows = /win/i.test(platform);
+
+ var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/);
+ if (presto_version) { presto_version = Number(presto_version[1]); }
+ if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
+ // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
+ var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));
+ var captureRightClick = gecko || (ie && ie_version >= 9);
+
+ function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
+
+ var rmClass = function(node, cls) {
+ var current = node.className;
+ var match = classTest(cls).exec(current);
+ if (match) {
+ var after = current.slice(match.index + match[0].length);
+ node.className = current.slice(0, match.index) + (after ? match[1] + after : "");
+ }
+ };
+
+ function removeChildren(e) {
+ for (var count = e.childNodes.length; count > 0; --count)
+ { e.removeChild(e.firstChild); }
+ return e
+ }
+
+ function removeChildrenAndAdd(parent, e) {
+ return removeChildren(parent).appendChild(e)
+ }
+
+ function elt(tag, content, className, style) {
+ var e = document.createElement(tag);
+ if (className) { e.className = className; }
+ if (style) { e.style.cssText = style; }
+ if (typeof content == "string") { e.appendChild(document.createTextNode(content)); }
+ else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } }
+ return e
+ }
+ // wrapper for elt, which removes the elt from the accessibility tree
+ function eltP(tag, content, className, style) {
+ var e = elt(tag, content, className, style);
+ e.setAttribute("role", "presentation");
+ return e
+ }
+
+ var range;
+ if (document.createRange) { range = function(node, start, end, endNode) {
+ var r = document.createRange();
+ r.setEnd(endNode || node, end);
+ r.setStart(node, start);
+ return r
+ }; }
+ else { range = function(node, start, end) {
+ var r = document.body.createTextRange();
+ try { r.moveToElementText(node.parentNode); }
+ catch(e) { return r }
+ r.collapse(true);
+ r.moveEnd("character", end);
+ r.moveStart("character", start);
+ return r
+ }; }
+
+ function contains(parent, child) {
+ if (child.nodeType == 3) // Android browser always returns false when child is a textnode
+ { child = child.parentNode; }
+ if (parent.contains)
+ { return parent.contains(child) }
+ do {
+ if (child.nodeType == 11) { child = child.host; }
+ if (child == parent) { return true }
+ } while (child = child.parentNode)
+ }
+
+ function activeElt() {
+ // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement.
+ // IE < 10 will throw when accessed while the page is loading or in an iframe.
+ // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable.
+ var activeElement;
+ try {
+ activeElement = document.activeElement;
+ } catch(e) {
+ activeElement = document.body || null;
+ }
+ while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement)
+ { activeElement = activeElement.shadowRoot.activeElement; }
+ return activeElement
+ }
+
+ function addClass(node, cls) {
+ var current = node.className;
+ if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls; }
+ }
+ function joinClasses(a, b) {
+ var as = a.split(" ");
+ for (var i = 0; i < as.length; i++)
+ { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i]; } }
+ return b
+ }
+
+ var selectInput = function(node) { node.select(); };
+ if (ios) // Mobile Safari apparently has a bug where select() is broken.
+ { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; }
+ else if (ie) // Suppress mysterious IE10 errors
+ { selectInput = function(node) { try { node.select(); } catch(_e) {} }; }
+
+ function bind(f) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function(){return f.apply(null, args)}
+ }
+
+ function copyObj(obj, target, overwrite) {
+ if (!target) { target = {}; }
+ for (var prop in obj)
+ { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
+ { target[prop] = obj[prop]; } }
+ return target
+ }
+
+ // Counts the column offset in a string, taking tabs into account.
+ // Used mostly to find indentation.
+ function countColumn(string, end, tabSize, startIndex, startValue) {
+ if (end == null) {
+ end = string.search(/[^\s\u00a0]/);
+ if (end == -1) { end = string.length; }
+ }
+ for (var i = startIndex || 0, n = startValue || 0;;) {
+ var nextTab = string.indexOf("\t", i);
+ if (nextTab < 0 || nextTab >= end)
+ { return n + (end - i) }
+ n += nextTab - i;
+ n += tabSize - (n % tabSize);
+ i = nextTab + 1;
+ }
+ }
+
+ var Delayed = function() {this.id = null;};
+ Delayed.prototype.set = function (ms, f) {
+ clearTimeout(this.id);
+ this.id = setTimeout(f, ms);
+ };
+
+ function indexOf(array, elt) {
+ for (var i = 0; i < array.length; ++i)
+ { if (array[i] == elt) { return i } }
+ return -1
+ }
+
+ // Number of pixels added to scroller and sizer to hide scrollbar
+ var scrollerGap = 30;
+
+ // Returned or thrown by various protocols to signal 'I'm not
+ // handling this'.
+ var Pass = {toString: function(){return "CodeMirror.Pass"}};
+
+ // Reused option objects for setSelection & friends
+ var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"};
+
+ // The inverse of countColumn -- find the offset that corresponds to
+ // a particular column.
+ function findColumn(string, goal, tabSize) {
+ for (var pos = 0, col = 0;;) {
+ var nextTab = string.indexOf("\t", pos);
+ if (nextTab == -1) { nextTab = string.length; }
+ var skipped = nextTab - pos;
+ if (nextTab == string.length || col + skipped >= goal)
+ { return pos + Math.min(skipped, goal - col) }
+ col += nextTab - pos;
+ col += tabSize - (col % tabSize);
+ pos = nextTab + 1;
+ if (col >= goal) { return pos }
+ }
+ }
+
+ var spaceStrs = [""];
+ function spaceStr(n) {
+ while (spaceStrs.length <= n)
+ { spaceStrs.push(lst(spaceStrs) + " "); }
+ return spaceStrs[n]
+ }
+
+ function lst(arr) { return arr[arr.length-1] }
+
+ function map(array, f) {
+ var out = [];
+ for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); }
+ return out
+ }
+
+ function insertSorted(array, value, score) {
+ var pos = 0, priority = score(value);
+ while (pos < array.length && score(array[pos]) <= priority) { pos++; }
+ array.splice(pos, 0, value);
+ }
+
+ function nothing() {}
+
+ function createObj(base, props) {
+ var inst;
+ if (Object.create) {
+ inst = Object.create(base);
+ } else {
+ nothing.prototype = base;
+ inst = new nothing();
+ }
+ if (props) { copyObj(props, inst); }
+ return inst
+ }
+
+ var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
+ function isWordCharBasic(ch) {
+ return /\w/.test(ch) || ch > "\x80" &&
+ (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))
+ }
+ function isWordChar(ch, helper) {
+ if (!helper) { return isWordCharBasic(ch) }
+ if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true }
+ return helper.test(ch)
+ }
+
+ function isEmpty(obj) {
+ for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } }
+ return true
+ }
+
+ // Extending unicode characters. A series of a non-extending char +
+ // any number of extending chars is treated as a single unit as far
+ // as editing and measuring is concerned. This is not fully correct,
+ // since some scripts/fonts/browsers also treat other configurations
+ // of code points as a group.
+ var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;
+ function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) }
+
+ // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range.
+ function skipExtendingChars(str, pos, dir) {
+ while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; }
+ return pos
+ }
+
+ // Returns the value from the range [`from`; `to`] that satisfies
+ // `pred` and is closest to `from`. Assumes that at least `to`
+ // satisfies `pred`. Supports `from` being greater than `to`.
+ function findFirst(pred, from, to) {
+ // At any point we are certain `to` satisfies `pred`, don't know
+ // whether `from` does.
+ var dir = from > to ? -1 : 1;
+ for (;;) {
+ if (from == to) { return from }
+ var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF);
+ if (mid == from) { return pred(mid) ? from : to }
+ if (pred(mid)) { to = mid; }
+ else { from = mid + dir; }
+ }
+ }
+
+ // BIDI HELPERS
+
+ function iterateBidiSections(order, from, to, f) {
+ if (!order) { return f(from, to, "ltr", 0) }
+ var found = false;
+ for (var i = 0; i < order.length; ++i) {
+ var part = order[i];
+ if (part.from < to && part.to > from || from == to && part.to == from) {
+ f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i);
+ found = true;
+ }
+ }
+ if (!found) { f(from, to, "ltr"); }
+ }
+
+ var bidiOther = null;
+ function getBidiPartAt(order, ch, sticky) {
+ var found;
+ bidiOther = null;
+ for (var i = 0; i < order.length; ++i) {
+ var cur = order[i];
+ if (cur.from < ch && cur.to > ch) { return i }
+ if (cur.to == ch) {
+ if (cur.from != cur.to && sticky == "before") { found = i; }
+ else { bidiOther = i; }
+ }
+ if (cur.from == ch) {
+ if (cur.from != cur.to && sticky != "before") { found = i; }
+ else { bidiOther = i; }
+ }
+ }
+ return found != null ? found : bidiOther
+ }
+
+ // Bidirectional ordering algorithm
+ // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
+ // that this (partially) implements.
+
+ // One-char codes used for character types:
+ // L (L): Left-to-Right
+ // R (R): Right-to-Left
+ // r (AL): Right-to-Left Arabic
+ // 1 (EN): European Number
+ // + (ES): European Number Separator
+ // % (ET): European Number Terminator
+ // n (AN): Arabic Number
+ // , (CS): Common Number Separator
+ // m (NSM): Non-Spacing Mark
+ // b (BN): Boundary Neutral
+ // s (B): Paragraph Separator
+ // t (S): Segment Separator
+ // w (WS): Whitespace
+ // N (ON): Other Neutrals
+
+ // Returns null if characters are ordered as they appear
+ // (left-to-right), or an array of sections ({from, to, level}
+ // objects) in the order in which they occur visually.
+ var bidiOrdering = (function() {
+ // Character types for codepoints 0 to 0xff
+ var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";
+ // Character types for codepoints 0x600 to 0x6f9
+ var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111";
+ function charType(code) {
+ if (code <= 0xf7) { return lowTypes.charAt(code) }
+ else if (0x590 <= code && code <= 0x5f4) { return "R" }
+ else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) }
+ else if (0x6ee <= code && code <= 0x8ac) { return "r" }
+ else if (0x2000 <= code && code <= 0x200b) { return "w" }
+ else if (code == 0x200c) { return "b" }
+ else { return "L" }
+ }
+
+ var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
+ var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
+
+ function BidiSpan(level, from, to) {
+ this.level = level;
+ this.from = from; this.to = to;
+ }
+
+ return function(str, direction) {
+ var outerType = direction == "ltr" ? "L" : "R";
+
+ if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false }
+ var len = str.length, types = [];
+ for (var i = 0; i < len; ++i)
+ { types.push(charType(str.charCodeAt(i))); }
+
+ // W1. Examine each non-spacing mark (NSM) in the level run, and
+ // change the type of the NSM to the type of the previous
+ // character. If the NSM is at the start of the level run, it will
+ // get the type of sor.
+ for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) {
+ var type = types[i$1];
+ if (type == "m") { types[i$1] = prev; }
+ else { prev = type; }
+ }
+
+ // W2. Search backwards from each instance of a European number
+ // until the first strong type (R, L, AL, or sor) is found. If an
+ // AL is found, change the type of the European number to Arabic
+ // number.
+ // W3. Change all ALs to R.
+ for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) {
+ var type$1 = types[i$2];
+ if (type$1 == "1" && cur == "r") { types[i$2] = "n"; }
+ else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R"; } }
+ }
+
+ // W4. A single European separator between two European numbers
+ // changes to a European number. A single common separator between
+ // two numbers of the same type changes to that type.
+ for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) {
+ var type$2 = types[i$3];
+ if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1"; }
+ else if (type$2 == "," && prev$1 == types[i$3+1] &&
+ (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1; }
+ prev$1 = type$2;
+ }
+
+ // W5. A sequence of European terminators adjacent to European
+ // numbers changes to all European numbers.
+ // W6. Otherwise, separators and terminators change to Other
+ // Neutral.
+ for (var i$4 = 0; i$4 < len; ++i$4) {
+ var type$3 = types[i$4];
+ if (type$3 == ",") { types[i$4] = "N"; }
+ else if (type$3 == "%") {
+ var end = (void 0);
+ for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {}
+ var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N";
+ for (var j = i$4; j < end; ++j) { types[j] = replace; }
+ i$4 = end - 1;
+ }
+ }
+
+ // W7. Search backwards from each instance of a European number
+ // until the first strong type (R, L, or sor) is found. If an L is
+ // found, then change the type of the European number to L.
+ for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) {
+ var type$4 = types[i$5];
+ if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L"; }
+ else if (isStrong.test(type$4)) { cur$1 = type$4; }
+ }
+
+ // N1. A sequence of neutrals takes the direction of the
+ // surrounding strong text if the text on both sides has the same
+ // direction. European and Arabic numbers act as if they were R in
+ // terms of their influence on neutrals. Start-of-level-run (sor)
+ // and end-of-level-run (eor) are used at level run boundaries.
+ // N2. Any remaining neutrals take the embedding direction.
+ for (var i$6 = 0; i$6 < len; ++i$6) {
+ if (isNeutral.test(types[i$6])) {
+ var end$1 = (void 0);
+ for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {}
+ var before = (i$6 ? types[i$6-1] : outerType) == "L";
+ var after = (end$1 < len ? types[end$1] : outerType) == "L";
+ var replace$1 = before == after ? (before ? "L" : "R") : outerType;
+ for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; }
+ i$6 = end$1 - 1;
+ }
+ }
+
+ // Here we depart from the documented algorithm, in order to avoid
+ // building up an actual levels array. Since there are only three
+ // levels (0, 1, 2) in an implementation that doesn't take
+ // explicit embedding into account, we can build up the order on
+ // the fly, without following the level-based algorithm.
+ var order = [], m;
+ for (var i$7 = 0; i$7 < len;) {
+ if (countsAsLeft.test(types[i$7])) {
+ var start = i$7;
+ for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {}
+ order.push(new BidiSpan(0, start, i$7));
+ } else {
+ var pos = i$7, at = order.length;
+ for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {}
+ for (var j$2 = pos; j$2 < i$7;) {
+ if (countsAsNum.test(types[j$2])) {
+ if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); }
+ var nstart = j$2;
+ for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {}
+ order.splice(at, 0, new BidiSpan(2, nstart, j$2));
+ pos = j$2;
+ } else { ++j$2; }
+ }
+ if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); }
+ }
+ }
+ if (direction == "ltr") {
+ if (order[0].level == 1 && (m = str.match(/^\s+/))) {
+ order[0].from = m[0].length;
+ order.unshift(new BidiSpan(0, 0, m[0].length));
+ }
+ if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
+ lst(order).to -= m[0].length;
+ order.push(new BidiSpan(0, len - m[0].length, len));
+ }
+ }
+
+ return direction == "rtl" ? order.reverse() : order
+ }
+ })();
+
+ // Get the bidi ordering for the given line (and cache it). Returns
+ // false for lines that are fully left-to-right, and an array of
+ // BidiSpan objects otherwise.
+ function getOrder(line, direction) {
+ var order = line.order;
+ if (order == null) { order = line.order = bidiOrdering(line.text, direction); }
+ return order
+ }
+
+ // EVENT HANDLING
+
+ // Lightweight event framework. on/off also work on DOM nodes,
+ // registering native DOM handlers.
+
+ var noHandlers = [];
+
+ var on = function(emitter, type, f) {
+ if (emitter.addEventListener) {
+ emitter.addEventListener(type, f, false);
+ } else if (emitter.attachEvent) {
+ emitter.attachEvent("on" + type, f);
+ } else {
+ var map$$1 = emitter._handlers || (emitter._handlers = {});
+ map$$1[type] = (map$$1[type] || noHandlers).concat(f);
+ }
+ };
+
+ function getHandlers(emitter, type) {
+ return emitter._handlers && emitter._handlers[type] || noHandlers
+ }
+
+ function off(emitter, type, f) {
+ if (emitter.removeEventListener) {
+ emitter.removeEventListener(type, f, false);
+ } else if (emitter.detachEvent) {
+ emitter.detachEvent("on" + type, f);
+ } else {
+ var map$$1 = emitter._handlers, arr = map$$1 && map$$1[type];
+ if (arr) {
+ var index = indexOf(arr, f);
+ if (index > -1)
+ { map$$1[type] = arr.slice(0, index).concat(arr.slice(index + 1)); }
+ }
+ }
+ }
+
+ function signal(emitter, type /*, values...*/) {
+ var handlers = getHandlers(emitter, type);
+ if (!handlers.length) { return }
+ var args = Array.prototype.slice.call(arguments, 2);
+ for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); }
+ }
+
+ // The DOM events that CodeMirror handles can be overridden by
+ // registering a (non-DOM) handler on the editor for the event name,
+ // and preventDefault-ing the event in that handler.
+ function signalDOMEvent(cm, e, override) {
+ if (typeof e == "string")
+ { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; }
+ signal(cm, override || e.type, cm, e);
+ return e_defaultPrevented(e) || e.codemirrorIgnore
+ }
+
+ function signalCursorActivity(cm) {
+ var arr = cm._handlers && cm._handlers.cursorActivity;
+ if (!arr) { return }
+ var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);
+ for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1)
+ { set.push(arr[i]); } }
+ }
+
+ function hasHandler(emitter, type) {
+ return getHandlers(emitter, type).length > 0
+ }
+
+ // Add on and off methods to a constructor's prototype, to make
+ // registering events on such objects more convenient.
+ function eventMixin(ctor) {
+ ctor.prototype.on = function(type, f) {on(this, type, f);};
+ ctor.prototype.off = function(type, f) {off(this, type, f);};
+ }
+
+ // Due to the fact that we still support jurassic IE versions, some
+ // compatibility wrappers are needed.
+
+ function e_preventDefault(e) {
+ if (e.preventDefault) { e.preventDefault(); }
+ else { e.returnValue = false; }
+ }
+ function e_stopPropagation(e) {
+ if (e.stopPropagation) { e.stopPropagation(); }
+ else { e.cancelBubble = true; }
+ }
+ function e_defaultPrevented(e) {
+ return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false
+ }
+ function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
+
+ function e_target(e) {return e.target || e.srcElement}
+ function e_button(e) {
+ var b = e.which;
+ if (b == null) {
+ if (e.button & 1) { b = 1; }
+ else if (e.button & 2) { b = 3; }
+ else if (e.button & 4) { b = 2; }
+ }
+ if (mac && e.ctrlKey && b == 1) { b = 3; }
+ return b
+ }
+
+ // 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 && ie_version < 9) { return false }
+ var div = elt('div');
+ return "draggable" in div || "dragDrop" in div
+ }();
+
+ var zwspSupported;
+ function zeroWidthElement(measure) {
+ if (zwspSupported == null) {
+ var test = elt("span", "\u200b");
+ removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
+ if (measure.firstChild.offsetHeight != 0)
+ { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); }
+ }
+ var node = zwspSupported ? elt("span", "\u200b") :
+ elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
+ node.setAttribute("cm-text", "");
+ return node
+ }
+
+ // Feature-detect IE's crummy client rect reporting for bidi text
+ var badBidiRects;
+ function hasBadBidiRects(measure) {
+ if (badBidiRects != null) { return badBidiRects }
+ var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"));
+ var r0 = range(txt, 0, 1).getBoundingClientRect();
+ var r1 = range(txt, 1, 2).getBoundingClientRect();
+ removeChildren(measure);
+ if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780)
+ return badBidiRects = (r1.right - r0.right < 3)
+ }
+
+ // See if "".split is the broken IE version, if so, provide an
+ // alternative way to split lines.
+ var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) {
+ var pos = 0, result = [], l = string.length;
+ while (pos <= l) {
+ var nl = string.indexOf("\n", pos);
+ if (nl == -1) { nl = string.length; }
+ var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
+ var rt = line.indexOf("\r");
+ if (rt != -1) {
+ result.push(line.slice(0, rt));
+ pos += rt + 1;
+ } else {
+ result.push(line);
+ pos = nl + 1;
+ }
+ }
+ return result
+ } : function (string) { return string.split(/\r\n?|\n/); };
+
+ var hasSelection = window.getSelection ? function (te) {
+ try { return te.selectionStart != te.selectionEnd }
+ catch(e) { return false }
+ } : function (te) {
+ var range$$1;
+ try {range$$1 = te.ownerDocument.selection.createRange();}
+ catch(e) {}
+ if (!range$$1 || range$$1.parentElement() != te) { return false }
+ return range$$1.compareEndPoints("StartToEnd", range$$1) != 0
+ };
+
+ var hasCopyEvent = (function () {
+ var e = elt("div");
+ if ("oncopy" in e) { return true }
+ e.setAttribute("oncopy", "return;");
+ return typeof e.oncopy == "function"
+ })();
+
+ var badZoomedRects = null;
+ function hasBadZoomedRects(measure) {
+ if (badZoomedRects != null) { return badZoomedRects }
+ var node = removeChildrenAndAdd(measure, elt("span", "x"));
+ var normal = node.getBoundingClientRect();
+ var fromRange = range(node, 0, 1).getBoundingClientRect();
+ return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1
+ }
+
+ // Known modes, by name and by MIME
+ var modes = {}, mimeModes = {};
+
+ // Extra arguments are stored as the mode's dependencies, which is
+ // used by (legacy) mechanisms like loadmode.js to automatically
+ // load a mode. (Preferred mechanism is the require/define calls.)
+ function defineMode(name, mode) {
+ if (arguments.length > 2)
+ { mode.dependencies = Array.prototype.slice.call(arguments, 2); }
+ modes[name] = mode;
+ }
+
+ function defineMIME(mime, spec) {
+ mimeModes[mime] = spec;
+ }
+
+ // Given a MIME type, a {name, ...options} config object, or a name
+ // string, return a mode config object.
+ function resolveMode(spec) {
+ if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
+ spec = mimeModes[spec];
+ } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
+ var found = mimeModes[spec.name];
+ if (typeof found == "string") { found = {name: found}; }
+ spec = createObj(found, spec);
+ spec.name = found.name;
+ } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
+ return resolveMode("application/xml")
+ } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) {
+ return resolveMode("application/json")
+ }
+ if (typeof spec == "string") { return {name: spec} }
+ else { return spec || {name: "null"} }
+ }
+
+ // Given a mode spec (anything that resolveMode accepts), find and
+ // initialize an actual mode object.
+ function getMode(options, spec) {
+ spec = resolveMode(spec);
+ var mfactory = modes[spec.name];
+ if (!mfactory) { return getMode(options, "text/plain") }
+ var modeObj = mfactory(options, spec);
+ if (modeExtensions.hasOwnProperty(spec.name)) {
+ var exts = modeExtensions[spec.name];
+ for (var prop in exts) {
+ if (!exts.hasOwnProperty(prop)) { continue }
+ if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop]; }
+ modeObj[prop] = exts[prop];
+ }
+ }
+ modeObj.name = spec.name;
+ if (spec.helperType) { modeObj.helperType = spec.helperType; }
+ if (spec.modeProps) { for (var prop$1 in spec.modeProps)
+ { modeObj[prop$1] = spec.modeProps[prop$1]; } }
+
+ return modeObj
+ }
+
+ // This can be used to attach properties to mode objects from
+ // outside the actual mode definition.
+ var modeExtensions = {};
+ function extendMode(mode, properties) {
+ var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
+ copyObj(properties, exts);
+ }
+
+ 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
+ }
+
+ // Given a mode and a state (for that mode), find the inner mode and
+ // state at the position that the state refers to.
+ function innerMode(mode, state) {
+ var info;
+ while (mode.innerMode) {
+ info = mode.innerMode(state);
+ if (!info || info.mode == mode) { break }
+ state = info.state;
+ mode = info.mode;
+ }
+ return info || {mode: mode, state: state}
+ }
+
+ function startState(mode, a1, a2) {
+ return mode.startState ? mode.startState(a1, a2) : true
+ }
+
+ // STRING STREAM
+
+ // Fed to the mode parsers, provides helper functions to make
+ // parsers more succinct.
+
+ var StringStream = function(string, tabSize, lineOracle) {
+ this.pos = this.start = 0;
+ this.string = string;
+ this.tabSize = tabSize || 8;
+ this.lastColumnPos = this.lastColumnValue = 0;
+ this.lineStart = 0;
+ this.lineOracle = lineOracle;
+ };
+
+ StringStream.prototype.eol = function () {return this.pos >= this.string.length};
+ StringStream.prototype.sol = function () {return this.pos == this.lineStart};
+ StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined};
+ StringStream.prototype.next = function () {
+ if (this.pos < this.string.length)
+ { return this.string.charAt(this.pos++) }
+ };
+ StringStream.prototype.eat = function (match) {
+ var ch = this.string.charAt(this.pos);
+ var ok;
+ if (typeof match == "string") { ok = ch == match; }
+ else { ok = ch && (match.test ? match.test(ch) : match(ch)); }
+ if (ok) {++this.pos; return ch}
+ };
+ StringStream.prototype.eatWhile = function (match) {
+ var start = this.pos;
+ while (this.eat(match)){}
+ return this.pos > start
+ };
+ StringStream.prototype.eatSpace = function () {
+ var this$1 = this;
+
+ var start = this.pos;
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos; }
+ return this.pos > start
+ };
+ StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;};
+ StringStream.prototype.skipTo = function (ch) {
+ var found = this.string.indexOf(ch, this.pos);
+ if (found > -1) {this.pos = found; return true}
+ };
+ StringStream.prototype.backUp = function (n) {this.pos -= n;};
+ StringStream.prototype.column = function () {
+ if (this.lastColumnPos < this.start) {
+ this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
+ this.lastColumnPos = this.start;
+ }
+ return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)
+ };
+ StringStream.prototype.indentation = function () {
+ return countColumn(this.string, null, this.tabSize) -
+ (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)
+ };
+ StringStream.prototype.match = function (pattern, consume, caseInsensitive) {
+ if (typeof pattern == "string") {
+ var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; };
+ var substr = this.string.substr(this.pos, pattern.length);
+ if (cased(substr) == cased(pattern)) {
+ if (consume !== false) { this.pos += pattern.length; }
+ return true
+ }
+ } else {
+ var match = this.string.slice(this.pos).match(pattern);
+ if (match && match.index > 0) { return null }
+ if (match && consume !== false) { this.pos += match[0].length; }
+ return match
+ }
+ };
+ StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)};
+ StringStream.prototype.hideFirstChars = function (n, inner) {
+ this.lineStart += n;
+ try { return inner() }
+ finally { this.lineStart -= n; }
+ };
+ StringStream.prototype.lookAhead = function (n) {
+ var oracle = this.lineOracle;
+ return oracle && oracle.lookAhead(n)
+ };
+ StringStream.prototype.baseToken = function () {
+ var oracle = this.lineOracle;
+ return oracle && oracle.baseToken(this.pos)
+ };
+
+ // Find the line object corresponding to the given line number.
+ function getLine(doc, n) {
+ n -= doc.first;
+ if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") }
+ var chunk = doc;
+ 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]
+ }
+
+ // Get the part of a document between two positions, as an array of
+ // strings.
+ function getBetween(doc, start, end) {
+ var out = [], n = start.line;
+ doc.iter(start.line, end.line + 1, function (line) {
+ var text = line.text;
+ if (n == end.line) { text = text.slice(0, end.ch); }
+ if (n == start.line) { text = text.slice(start.ch); }
+ out.push(text);
+ ++n;
+ });
+ return out
+ }
+ // Get the lines between from and to, as array of strings.
+ function getLines(doc, from, to) {
+ var out = [];
+ doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value
+ return out
+ }
+
+ // Update the height of a line, propagating the height change
+ // upwards to parent nodes.
+ function updateLineHeight(line, height) {
+ var diff = height - line.height;
+ if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } }
+ }
+
+ // Given a line object, find its line number by walking up through
+ // its parent links.
+ function lineNo(line) {
+ if (line.parent == null) { return null }
+ var cur = line.parent, no = indexOf(cur.lines, line);
+ for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
+ for (var i = 0;; ++i) {
+ if (chunk.children[i] == cur) { break }
+ no += chunk.children[i].chunkSize();
+ }
+ }
+ return no + cur.first
+ }
+
+ // Find the line at the given vertical position, using the height
+ // information in the document tree.
+ function lineAtHeight(chunk, h) {
+ var n = chunk.first;
+ outer: do {
+ for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) {
+ var child = chunk.children[i$1], ch = child.height;
+ if (h < ch) { chunk = child; continue outer }
+ h -= ch;
+ n += child.chunkSize();
+ }
+ return n
+ } while (!chunk.lines)
+ var i = 0;
+ for (; i < chunk.lines.length; ++i) {
+ var line = chunk.lines[i], lh = line.height;
+ if (h < lh) { break }
+ h -= lh;
+ }
+ return n + i
+ }
+
+ function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size}
+
+ function lineNumberFor(options, i) {
+ return String(options.lineNumberFormatter(i + options.firstLineNumber))
+ }
+
+ // A Pos instance represents a position within the text.
+ function Pos(line, ch, sticky) {
+ if ( sticky === void 0 ) sticky = null;
+
+ if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) }
+ this.line = line;
+ this.ch = ch;
+ this.sticky = sticky;
+ }
+
+ // Compare two positions, return 0 if they are the same, a negative
+ // number when a is less, and a positive number otherwise.
+ function cmp(a, b) { return a.line - b.line || a.ch - b.ch }
+
+ function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 }
+
+ function copyPos(x) {return Pos(x.line, x.ch)}
+ function maxPos(a, b) { return cmp(a, b) < 0 ? b : a }
+ function minPos(a, b) { return cmp(a, b) < 0 ? a : b }
+
+ // Most of the external API clips given positions to make sure they
+ // actually exist within the document.
+ function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))}
+ function clipPos(doc, pos) {
+ if (pos.line < doc.first) { return Pos(doc.first, 0) }
+ var last = doc.first + doc.size - 1;
+ if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) }
+ return clipToLen(pos, getLine(doc, pos.line).text.length)
+ }
+ function clipToLen(pos, linelen) {
+ var ch = pos.ch;
+ if (ch == null || ch > linelen) { return Pos(pos.line, linelen) }
+ else if (ch < 0) { return Pos(pos.line, 0) }
+ else { return pos }
+ }
+ function clipPosArray(doc, array) {
+ var out = [];
+ for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); }
+ return out
+ }
+
+ var SavedContext = function(state, lookAhead) {
+ this.state = state;
+ this.lookAhead = lookAhead;
+ };
+
+ var Context = function(doc, state, line, lookAhead) {
+ this.state = state;
+ this.doc = doc;
+ this.line = line;
+ this.maxLookAhead = lookAhead || 0;
+ this.baseTokens = null;
+ this.baseTokenPos = 1;
+ };
+
+ Context.prototype.lookAhead = function (n) {
+ var line = this.doc.getLine(this.line + n);
+ if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; }
+ return line
+ };
+
+ Context.prototype.baseToken = function (n) {
+ var this$1 = this;
+
+ if (!this.baseTokens) { return null }
+ while (this.baseTokens[this.baseTokenPos] <= n)
+ { this$1.baseTokenPos += 2; }
+ var type = this.baseTokens[this.baseTokenPos + 1];
+ return {type: type && type.replace(/( |^)overlay .*/, ""),
+ size: this.baseTokens[this.baseTokenPos] - n}
+ };
+
+ Context.prototype.nextLine = function () {
+ this.line++;
+ if (this.maxLookAhead > 0) { this.maxLookAhead--; }
+ };
+
+ Context.fromSaved = function (doc, saved, line) {
+ if (saved instanceof SavedContext)
+ { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) }
+ else
+ { return new Context(doc, copyState(doc.mode, saved), line) }
+ };
+
+ Context.prototype.save = function (copy) {
+ var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state;
+ return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state
+ };
+
+
+ // Compute a style array (an array starting with a mode generation
+ // -- for invalidation -- followed by pairs of end positions and
+ // style strings), which is used to highlight the tokens on the
+ // line.
+ function highlightLine(cm, line, context, forceToEnd) {
+ // A styles array always starts with a number identifying the
+ // mode/overlays that it is based on (for easy invalidation).
+ var st = [cm.state.modeGen], lineClasses = {};
+ // Compute the base array of styles
+ runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); },
+ lineClasses, forceToEnd);
+ var state = context.state;
+
+ // Run overlays, adjust style array.
+ var loop = function ( o ) {
+ context.baseTokens = st;
+ var overlay = cm.state.overlays[o], i = 1, at = 0;
+ context.state = true;
+ runMode(cm, line.text, overlay.mode, context, function (end, style) {
+ var start = i;
+ // Ensure there's a token end at the current position, and that i points at it
+ while (at < end) {
+ var i_end = st[i];
+ if (i_end > end)
+ { st.splice(i, 1, end, st[i+1], i_end); }
+ i += 2;
+ at = Math.min(end, i_end);
+ }
+ if (!style) { return }
+ if (overlay.opaque) {
+ st.splice(start, i - start, end, "overlay " + style);
+ i = start + 2;
+ } else {
+ for (; start < i; start += 2) {
+ var cur = st[start+1];
+ st[start+1] = (cur ? cur + " " : "") + "overlay " + style;
+ }
+ }
+ }, lineClasses);
+ context.state = state;
+ context.baseTokens = null;
+ context.baseTokenPos = 1;
+ };
+
+ for (var o = 0; o < cm.state.overlays.length; ++o) loop( o );
+
+ return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}
+ }
+
+ function getLineStyles(cm, line, updateFrontier) {
+ if (!line.styles || line.styles[0] != cm.state.modeGen) {
+ var context = getContextBefore(cm, lineNo(line));
+ var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state);
+ var result = highlightLine(cm, line, context);
+ if (resetState) { context.state = resetState; }
+ line.stateAfter = context.save(!resetState);
+ line.styles = result.styles;
+ if (result.classes) { line.styleClasses = result.classes; }
+ else if (line.styleClasses) { line.styleClasses = null; }
+ if (updateFrontier === cm.doc.highlightFrontier)
+ { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); }
+ }
+ return line.styles
+ }
+
+ function getContextBefore(cm, n, precise) {
+ var doc = cm.doc, display = cm.display;
+ if (!doc.mode.startState) { return new Context(doc, true, n) }
+ var start = findStartLine(cm, n, precise);
+ var saved = start > doc.first && getLine(doc, start - 1).stateAfter;
+ var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start);
+
+ doc.iter(start, n, function (line) {
+ processLine(cm, line.text, context);
+ var pos = context.line;
+ line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null;
+ context.nextLine();
+ });
+ if (precise) { doc.modeFrontier = context.line; }
+ return context
+ }
+
+ // Lightweight form of highlight -- proceed over this line and
+ // update state, but don't save a style array. Used for lines that
+ // aren't currently visible.
+ function processLine(cm, text, context, startAt) {
+ var mode = cm.doc.mode;
+ var stream = new StringStream(text, cm.options.tabSize, context);
+ stream.start = stream.pos = startAt || 0;
+ if (text == "") { callBlankLine(mode, context.state); }
+ while (!stream.eol()) {
+ readToken(mode, stream, context.state);
+ stream.start = stream.pos;
+ }
+ }
+
+ function callBlankLine(mode, state) {
+ if (mode.blankLine) { return mode.blankLine(state) }
+ if (!mode.innerMode) { return }
+ var inner = innerMode(mode, state);
+ if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) }
+ }
+
+ function readToken(mode, stream, state, inner) {
+ for (var i = 0; i < 10; i++) {
+ if (inner) { inner[0] = innerMode(mode, state).mode; }
+ var style = mode.token(stream, state);
+ if (stream.pos > stream.start) { return style }
+ }
+ throw new Error("Mode " + mode.name + " failed to advance stream.")
+ }
+
+ var Token = function(stream, type, state) {
+ this.start = stream.start; this.end = stream.pos;
+ this.string = stream.current();
+ this.type = type || null;
+ this.state = state;
+ };
+
+ // Utility for getTokenAt and getLineTokens
+ function takeToken(cm, pos, precise, asArray) {
+ var doc = cm.doc, mode = doc.mode, style;
+ pos = clipPos(doc, pos);
+ var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise);
+ var stream = new StringStream(line.text, cm.options.tabSize, context), tokens;
+ if (asArray) { tokens = []; }
+ while ((asArray || stream.pos < pos.ch) && !stream.eol()) {
+ stream.start = stream.pos;
+ style = readToken(mode, stream, context.state);
+ if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); }
+ }
+ return asArray ? tokens : new Token(stream, style, context.state)
+ }
+
+ function extractLineClasses(type, output) {
+ if (type) { for (;;) {
+ var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/);
+ if (!lineClass) { break }
+ type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length);
+ var prop = lineClass[1] ? "bgClass" : "textClass";
+ if (output[prop] == null)
+ { output[prop] = lineClass[2]; }
+ else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop]))
+ { output[prop] += " " + lineClass[2]; }
+ } }
+ return type
+ }
+
+ // Run the given mode's parser over a line, calling f for each token.
+ function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) {
+ var flattenSpans = mode.flattenSpans;
+ if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; }
+ var curStart = 0, curStyle = null;
+ var stream = new StringStream(text, cm.options.tabSize, context), style;
+ var inner = cm.options.addModeClass && [null];
+ if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); }
+ while (!stream.eol()) {
+ if (stream.pos > cm.options.maxHighlightLength) {
+ flattenSpans = false;
+ if (forceToEnd) { processLine(cm, text, context, stream.pos); }
+ stream.pos = text.length;
+ style = null;
+ } else {
+ style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses);
+ }
+ if (inner) {
+ var mName = inner[0].name;
+ if (mName) { style = "m-" + (style ? mName + " " + style : mName); }
+ }
+ if (!flattenSpans || curStyle != style) {
+ while (curStart < stream.start) {
+ curStart = Math.min(stream.start, curStart + 5000);
+ f(curStart, curStyle);
+ }
+ curStyle = style;
+ }
+ stream.start = stream.pos;
+ }
+ while (curStart < stream.pos) {
+ // Webkit seems to refuse to render text nodes longer than 57444
+ // characters, and returns inaccurate measurements in nodes
+ // starting around 5000 chars.
+ var pos = Math.min(stream.pos, curStart + 5000);
+ f(pos, curStyle);
+ curStart = pos;
+ }
+ }
+
+ // Finds the line to start with when starting a parse. Tries to
+ // find a line with a stateAfter, so that it can start with a
+ // valid state. If that fails, it returns the line with the
+ // smallest indentation, which tends to need the least context to
+ // parse correctly.
+ function findStartLine(cm, n, precise) {
+ var minindent, minline, doc = cm.doc;
+ var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);
+ for (var search = n; search > lim; --search) {
+ if (search <= doc.first) { return doc.first }
+ var line = getLine(doc, search - 1), after = line.stateAfter;
+ if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier))
+ { return search }
+ var indented = countColumn(line.text, null, cm.options.tabSize);
+ if (minline == null || minindent > indented) {
+ minline = search - 1;
+ minindent = indented;
+ }
+ }
+ return minline
+ }
+
+ function retreatFrontier(doc, n) {
+ doc.modeFrontier = Math.min(doc.modeFrontier, n);
+ if (doc.highlightFrontier < n - 10) { return }
+ var start = doc.first;
+ for (var line = n - 1; line > start; line--) {
+ var saved = getLine(doc, line).stateAfter;
+ // change is on 3
+ // state on line 1 looked ahead 2 -- so saw 3
+ // test 1 + 2 < 3 should cover this
+ if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) {
+ start = line + 1;
+ break
+ }
+ }
+ doc.highlightFrontier = Math.min(doc.highlightFrontier, start);
+ }
+
+ // Optimize some code when these features are not used.
+ var sawReadOnlySpans = false, sawCollapsedSpans = false;
+
+ function seeReadOnlySpans() {
+ sawReadOnlySpans = true;
+ }
+
+ function seeCollapsedSpans() {
+ sawCollapsedSpans = true;
+ }
+
+ // TEXTMARKER SPANS
+
+ function MarkedSpan(marker, from, to) {
+ this.marker = marker;
+ this.from = from; this.to = to;
+ }
+
+ // Search an array of spans for a span matching the given marker.
+ function getMarkedSpanFor(spans, marker) {
+ if (spans) { for (var i = 0; i < spans.length; ++i) {
+ var span = spans[i];
+ if (span.marker == marker) { return span }
+ } }
+ }
+ // Remove a span from an array, returning undefined if no spans are
+ // left (we don't store arrays for lines without spans).
+ function removeMarkedSpan(spans, span) {
+ var r;
+ for (var i = 0; i < spans.length; ++i)
+ { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } }
+ return r
+ }
+ // Add a span to a line.
+ function addMarkedSpan(line, span) {
+ line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
+ span.marker.attachLine(line);
+ }
+
+ // Used for the algorithm that adjusts markers for a change in the
+ // document. These functions cut an array of spans at a given
+ // character position, returning an array of remaining chunks (or
+ // undefined if nothing remains).
+ function markedSpansBefore(old, startCh, isInsert) {
+ var nw;
+ if (old) { for (var i = 0; i < old.length; ++i) {
+ var span = old[i], marker = span.marker;
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
+ if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) {
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh)
+ ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to));
+ }
+ } }
+ return nw
+ }
+ function markedSpansAfter(old, endCh, isInsert) {
+ var nw;
+ if (old) { for (var i = 0; i < old.length; ++i) {
+ var span = old[i], marker = span.marker;
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
+ if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) {
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh)
+ ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,
+ span.to == null ? null : span.to - endCh));
+ }
+ } }
+ return nw
+ }
+
+ // Given a change object, compute the new set of marker spans that
+ // cover the line in which the change took place. Removes spans
+ // entirely within the change, reconnects spans belonging to the
+ // same marker that appear on both sides of the change, and cuts off
+ // spans partially within the change. Returns an array of span
+ // arrays with one element for each line in (after) the change.
+ function stretchSpansOverChange(doc, change) {
+ if (change.full) { return null }
+ var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
+ var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
+ if (!oldFirst && !oldLast) { return null }
+
+ var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0;
+ // Get the spans that 'stick out' on both sides
+ var first = markedSpansBefore(oldFirst, startCh, isInsert);
+ var last = markedSpansAfter(oldLast, endCh, isInsert);
+
+ // Next, merge those two ends
+ var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
+ if (first) {
+ // Fix up .to properties of first
+ for (var i = 0; i < first.length; ++i) {
+ var span = first[i];
+ if (span.to == null) {
+ var found = getMarkedSpanFor(last, span.marker);
+ if (!found) { span.to = startCh; }
+ else if (sameLine) { span.to = found.to == null ? null : found.to + offset; }
+ }
+ }
+ }
+ if (last) {
+ // Fix up .from in last (or move them into first in case of sameLine)
+ for (var i$1 = 0; i$1 < last.length; ++i$1) {
+ var span$1 = last[i$1];
+ if (span$1.to != null) { span$1.to += offset; }
+ if (span$1.from == null) {
+ var found$1 = getMarkedSpanFor(first, span$1.marker);
+ if (!found$1) {
+ span$1.from = offset;
+ if (sameLine) { (first || (first = [])).push(span$1); }
+ }
+ } else {
+ span$1.from += offset;
+ if (sameLine) { (first || (first = [])).push(span$1); }
+ }
+ }
+ }
+ // Make sure we didn't create any zero-length spans
+ if (first) { first = clearEmptySpans(first); }
+ if (last && last != first) { last = clearEmptySpans(last); }
+
+ var newMarkers = [first];
+ if (!sameLine) {
+ // Fill gap with whole-line-spans
+ var gap = change.text.length - 2, gapMarkers;
+ if (gap > 0 && first)
+ { for (var i$2 = 0; i$2 < first.length; ++i$2)
+ { if (first[i$2].to == null)
+ { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } }
+ for (var i$3 = 0; i$3 < gap; ++i$3)
+ { newMarkers.push(gapMarkers); }
+ newMarkers.push(last);
+ }
+ return newMarkers
+ }
+
+ // Remove spans that are empty and don't have a clearWhenEmpty
+ // option of false.
+ function clearEmptySpans(spans) {
+ for (var i = 0; i < spans.length; ++i) {
+ var span = spans[i];
+ if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)
+ { spans.splice(i--, 1); }
+ }
+ if (!spans.length) { return null }
+ return spans
+ }
+
+ // Used to 'clip' out readOnly ranges when making a change.
+ function removeReadOnlyRanges(doc, from, to) {
+ var markers = null;
+ doc.iter(from.line, to.line + 1, function (line) {
+ if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {
+ var mark = line.markedSpans[i].marker;
+ if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
+ { (markers || (markers = [])).push(mark); }
+ } }
+ });
+ if (!markers) { return null }
+ var parts = [{from: from, to: to}];
+ for (var i = 0; i < markers.length; ++i) {
+ var mk = markers[i], m = mk.find(0);
+ for (var j = 0; j < parts.length; ++j) {
+ var p = parts[j];
+ if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue }
+ var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to);
+ if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)
+ { newParts.push({from: p.from, to: m.from}); }
+ if (dto > 0 || !mk.inclusiveRight && !dto)
+ { newParts.push({from: m.to, to: p.to}); }
+ parts.splice.apply(parts, newParts);
+ j += newParts.length - 3;
+ }
+ }
+ return parts
+ }
+
+ // Connect or disconnect spans from a line.
+ function detachMarkedSpans(line) {
+ var spans = line.markedSpans;
+ if (!spans) { return }
+ for (var i = 0; i < spans.length; ++i)
+ { spans[i].marker.detachLine(line); }
+ line.markedSpans = null;
+ }
+ function attachMarkedSpans(line, spans) {
+ if (!spans) { return }
+ for (var i = 0; i < spans.length; ++i)
+ { spans[i].marker.attachLine(line); }
+ line.markedSpans = spans;
+ }
+
+ // Helpers used when computing which overlapping collapsed span
+ // counts as the larger one.
+ function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 }
+ function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 }
+
+ // Returns a number indicating which of two overlapping collapsed
+ // spans is larger (and thus includes the other). Falls back to
+ // comparing ids when the spans cover exactly the same range.
+ function compareCollapsedMarkers(a, b) {
+ var lenDiff = a.lines.length - b.lines.length;
+ if (lenDiff != 0) { return lenDiff }
+ var aPos = a.find(), bPos = b.find();
+ var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);
+ if (fromCmp) { return -fromCmp }
+ var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);
+ if (toCmp) { return toCmp }
+ return b.id - a.id
+ }
+
+ // Find out whether a line ends or starts in a collapsed span. If
+ // so, return the marker for that span.
+ function collapsedSpanAtSide(line, start) {
+ var sps = sawCollapsedSpans && line.markedSpans, found;
+ if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {
+ sp = sps[i];
+ if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
+ (!found || compareCollapsedMarkers(found, sp.marker) < 0))
+ { found = sp.marker; }
+ } }
+ return found
+ }
+ function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) }
+ function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) }
+
+ function collapsedSpanAround(line, ch) {
+ var sps = sawCollapsedSpans && line.markedSpans, found;
+ if (sps) { for (var i = 0; i < sps.length; ++i) {
+ var sp = sps[i];
+ if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) &&
+ (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; }
+ } }
+ return found
+ }
+
+ // Test whether there exists a collapsed span that partially
+ // overlaps (covers the start or end, but not both) of a new span.
+ // Such overlap is not allowed.
+ function conflictingCollapsedRange(doc, lineNo$$1, from, to, marker) {
+ var line = getLine(doc, lineNo$$1);
+ var sps = sawCollapsedSpans && line.markedSpans;
+ if (sps) { for (var i = 0; i < sps.length; ++i) {
+ var sp = sps[i];
+ if (!sp.marker.collapsed) { continue }
+ var found = sp.marker.find(0);
+ var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
+ var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
+ if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue }
+ if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) ||
+ fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0))
+ { return true }
+ } }
+ }
+
+ // A visual line is a line as drawn on the screen. Folding, for
+ // example, can cause multiple logical lines to appear on the same
+ // visual line. This finds the start of the visual line that the
+ // given line is part of (usually that is the line itself).
+ function visualLine(line) {
+ var merged;
+ while (merged = collapsedSpanAtStart(line))
+ { line = merged.find(-1, true).line; }
+ return line
+ }
+
+ function visualLineEnd(line) {
+ var merged;
+ while (merged = collapsedSpanAtEnd(line))
+ { line = merged.find(1, true).line; }
+ return line
+ }
+
+ // Returns an array of logical lines that continue the visual line
+ // started by the argument, or undefined if there are no such lines.
+ function visualLineContinued(line) {
+ var merged, lines;
+ while (merged = collapsedSpanAtEnd(line)) {
+ line = merged.find(1, true).line
+ ;(lines || (lines = [])).push(line);
+ }
+ return lines
+ }
+
+ // Get the line number of the start of the visual line that the
+ // given line number is part of.
+ function visualLineNo(doc, lineN) {
+ var line = getLine(doc, lineN), vis = visualLine(line);
+ if (line == vis) { return lineN }
+ return lineNo(vis)
+ }
+
+ // Get the line number of the start of the next visual line after
+ // the given line.
+ function visualLineEndNo(doc, lineN) {
+ if (lineN > doc.lastLine()) { return lineN }
+ var line = getLine(doc, lineN), merged;
+ if (!lineIsHidden(doc, line)) { return lineN }
+ while (merged = collapsedSpanAtEnd(line))
+ { line = merged.find(1, true).line; }
+ return lineNo(line) + 1
+ }
+
+ // Compute whether a line is hidden. Lines count as hidden when they
+ // are part of a visual line that starts with another line, or when
+ // they are entirely covered by collapsed, non-widget span.
+ function lineIsHidden(doc, line) {
+ var sps = sawCollapsedSpans && line.markedSpans;
+ if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {
+ sp = sps[i];
+ if (!sp.marker.collapsed) { continue }
+ if (sp.from == null) { return true }
+ if (sp.marker.widgetNode) { continue }
+ if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
+ { return true }
+ } }
+ }
+ function lineIsHiddenInner(doc, line, span) {
+ if (span.to == null) {
+ var end = span.marker.find(1, true);
+ return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker))
+ }
+ if (span.marker.inclusiveRight && span.to == line.text.length)
+ { return true }
+ for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) {
+ sp = line.markedSpans[i];
+ if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&
+ (sp.to == null || sp.to != span.from) &&
+ (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
+ lineIsHiddenInner(doc, line, sp)) { return true }
+ }
+ }
+
+ // Find the height above the given line.
+ function heightAtLine(lineObj) {
+ lineObj = visualLine(lineObj);
+
+ var h = 0, chunk = lineObj.parent;
+ for (var i = 0; i < chunk.lines.length; ++i) {
+ var line = chunk.lines[i];
+ if (line == lineObj) { break }
+ else { h += line.height; }
+ }
+ for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
+ for (var i$1 = 0; i$1 < p.children.length; ++i$1) {
+ var cur = p.children[i$1];
+ if (cur == chunk) { break }
+ else { h += cur.height; }
+ }
+ }
+ return h
+ }
+
+ // Compute the character length of a line, taking into account
+ // collapsed ranges (see markText) that might hide parts, and join
+ // other lines onto it.
+ function lineLength(line) {
+ if (line.height == 0) { return 0 }
+ var len = line.text.length, merged, cur = line;
+ while (merged = collapsedSpanAtStart(cur)) {
+ var found = merged.find(0, true);
+ cur = found.from.line;
+ len += found.from.ch - found.to.ch;
+ }
+ cur = line;
+ while (merged = collapsedSpanAtEnd(cur)) {
+ var found$1 = merged.find(0, true);
+ len -= cur.text.length - found$1.from.ch;
+ cur = found$1.to.line;
+ len += cur.text.length - found$1.to.ch;
+ }
+ return len
+ }
+
+ // Find the longest line in the document.
+ function findMaxLine(cm) {
+ var d = cm.display, doc = cm.doc;
+ d.maxLine = getLine(doc, doc.first);
+ d.maxLineLength = lineLength(d.maxLine);
+ d.maxLineChanged = true;
+ doc.iter(function (line) {
+ var len = lineLength(line);
+ if (len > d.maxLineLength) {
+ d.maxLineLength = len;
+ d.maxLine = line;
+ }
+ });
+ }
+
+ // LINE DATA STRUCTURE
+
+ // Line objects. These hold state related to a line, including
+ // highlighting info (the styles array).
+ var Line = function(text, markedSpans, estimateHeight) {
+ this.text = text;
+ attachMarkedSpans(this, markedSpans);
+ this.height = estimateHeight ? estimateHeight(this) : 1;
+ };
+
+ Line.prototype.lineNo = function () { return lineNo(this) };
+ eventMixin(Line);
+
+ // Change the content (text, markers) of a line. Automatically
+ // invalidates cached information and tries to re-estimate the
+ // line's height.
+ function updateLine(line, text, markedSpans, estimateHeight) {
+ line.text = text;
+ if (line.stateAfter) { line.stateAfter = null; }
+ if (line.styles) { line.styles = null; }
+ if (line.order != null) { line.order = null; }
+ detachMarkedSpans(line);
+ attachMarkedSpans(line, markedSpans);
+ var estHeight = estimateHeight ? estimateHeight(line) : 1;
+ if (estHeight != line.height) { updateLineHeight(line, estHeight); }
+ }
+
+ // Detach a line from the document tree and its markers.
+ function cleanUpLine(line) {
+ line.parent = null;
+ detachMarkedSpans(line);
+ }
+
+ // Convert a style as returned by a mode (either null, or a string
+ // containing one or more styles) to a CSS style. This is cached,
+ // and also looks for line-wide styles.
+ var styleToClassCache = {}, styleToClassCacheWithMode = {};
+ function interpretTokenStyle(style, options) {
+ if (!style || /^\s*$/.test(style)) { return null }
+ var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;
+ return cache[style] ||
+ (cache[style] = style.replace(/\S+/g, "cm-$&"))
+ }
+
+ // Render the DOM representation of the text of a line. Also builds
+ // up a 'line map', which points at the DOM nodes that represent
+ // specific stretches of text, and is used by the measuring code.
+ // The returned object contains the DOM node, this map, and
+ // information about line-wide styles that were set by the mode.
+ function buildLineContent(cm, lineView) {
+ // The padding-right forces the element to have a 'border', which
+ // is needed on Webkit to be able to get line-level bounding
+ // rectangles for it (in measureChar).
+ var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null);
+ var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content,
+ col: 0, pos: 0, cm: cm,
+ trailingSpace: false,
+ splitSpaces: cm.getOption("lineWrapping")};
+ lineView.measure = {};
+
+ // Iterate over the logical lines that make up this visual line.
+ for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
+ var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0);
+ builder.pos = 0;
+ builder.addToken = buildToken;
+ // Optionally wire in some hacks into the token-rendering
+ // algorithm, to deal with browser quirks.
+ if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction)))
+ { builder.addToken = buildTokenBadBidi(builder.addToken, order); }
+ builder.map = [];
+ var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line);
+ insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate));
+ if (line.styleClasses) {
+ if (line.styleClasses.bgClass)
+ { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); }
+ if (line.styleClasses.textClass)
+ { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); }
+ }
+
+ // Ensure at least a single node is present, for measuring.
+ if (builder.map.length == 0)
+ { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); }
+
+ // Store the map and a cache object for the current logical line
+ if (i == 0) {
+ lineView.measure.map = builder.map;
+ lineView.measure.cache = {};
+ } else {
+ (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map)
+ ;(lineView.measure.caches || (lineView.measure.caches = [])).push({});
+ }
+ }
+
+ // See issue #2901
+ if (webkit) {
+ var last = builder.content.lastChild;
+ if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab")))
+ { builder.content.className = "cm-tab-wrap-hack"; }
+ }
+
+ signal(cm, "renderLine", cm, lineView.line, builder.pre);
+ if (builder.pre.className)
+ { builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); }
+
+ return builder
+ }
+
+ function defaultSpecialCharPlaceholder(ch) {
+ var token = elt("span", "\u2022", "cm-invalidchar");
+ token.title = "\\u" + ch.charCodeAt(0).toString(16);
+ token.setAttribute("aria-label", token.title);
+ return token
+ }
+
+ // Build up the DOM representation for a single token, and add it to
+ // the line map. Takes care to render special characters separately.
+ function buildToken(builder, text, style, startStyle, endStyle, css, attributes) {
+ if (!text) { return }
+ var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text;
+ var special = builder.cm.state.specialChars, mustWrap = false;
+ var content;
+ if (!special.test(text)) {
+ builder.col += text.length;
+ content = document.createTextNode(displayText);
+ builder.map.push(builder.pos, builder.pos + text.length, content);
+ if (ie && ie_version < 9) { mustWrap = true; }
+ builder.pos += text.length;
+ } else {
+ content = document.createDocumentFragment();
+ var pos = 0;
+ while (true) {
+ special.lastIndex = pos;
+ var m = special.exec(text);
+ var skipped = m ? m.index - pos : text.length - pos;
+ if (skipped) {
+ var txt = document.createTextNode(displayText.slice(pos, pos + skipped));
+ if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])); }
+ else { content.appendChild(txt); }
+ builder.map.push(builder.pos, builder.pos + skipped, txt);
+ builder.col += skipped;
+ builder.pos += skipped;
+ }
+ if (!m) { break }
+ pos += skipped + 1;
+ var txt$1 = (void 0);
+ if (m[0] == "\t") {
+ var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
+ txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
+ txt$1.setAttribute("role", "presentation");
+ txt$1.setAttribute("cm-text", "\t");
+ builder.col += tabWidth;
+ } else if (m[0] == "\r" || m[0] == "\n") {
+ txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar"));
+ txt$1.setAttribute("cm-text", m[0]);
+ builder.col += 1;
+ } else {
+ txt$1 = builder.cm.options.specialCharPlaceholder(m[0]);
+ txt$1.setAttribute("cm-text", m[0]);
+ if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])); }
+ else { content.appendChild(txt$1); }
+ builder.col += 1;
+ }
+ builder.map.push(builder.pos, builder.pos + 1, txt$1);
+ builder.pos++;
+ }
+ }
+ builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32;
+ if (style || startStyle || endStyle || mustWrap || css) {
+ var fullStyle = style || "";
+ if (startStyle) { fullStyle += startStyle; }
+ if (endStyle) { fullStyle += endStyle; }
+ var token = elt("span", [content], fullStyle, css);
+ if (attributes) {
+ for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class")
+ { token.setAttribute(attr, attributes[attr]); } }
+ }
+ return builder.content.appendChild(token)
+ }
+ builder.content.appendChild(content);
+ }
+
+ // Change some spaces to NBSP to prevent the browser from collapsing
+ // trailing spaces at the end of a line when rendering text (issue #1362).
+ function splitSpaces(text, trailingBefore) {
+ if (text.length > 1 && !/ /.test(text)) { return text }
+ var spaceBefore = trailingBefore, result = "";
+ for (var i = 0; i < text.length; i++) {
+ var ch = text.charAt(i);
+ if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))
+ { ch = "\u00a0"; }
+ result += ch;
+ spaceBefore = ch == " ";
+ }
+ return result
+ }
+
+ // Work around nonsense dimensions being reported for stretches of
+ // right-to-left text.
+ function buildTokenBadBidi(inner, order) {
+ return function (builder, text, style, startStyle, endStyle, css, attributes) {
+ style = style ? style + " cm-force-border" : "cm-force-border";
+ var start = builder.pos, end = start + text.length;
+ for (;;) {
+ // Find the part that overlaps with the start of this text
+ var part = (void 0);
+ for (var i = 0; i < order.length; i++) {
+ part = order[i];
+ if (part.to > start && part.from <= start) { break }
+ }
+ if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, css, attributes) }
+ inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes);
+ startStyle = null;
+ text = text.slice(part.to - start);
+ start = part.to;
+ }
+ }
+ }
+
+ function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
+ var widget = !ignoreWidget && marker.widgetNode;
+ if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); }
+ if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {
+ if (!widget)
+ { widget = builder.content.appendChild(document.createElement("span")); }
+ widget.setAttribute("cm-marker", marker.id);
+ }
+ if (widget) {
+ builder.cm.display.input.setUneditable(widget);
+ builder.content.appendChild(widget);
+ }
+ builder.pos += size;
+ builder.trailingSpace = false;
+ }
+
+ // Outputs a number of spans to make up a line, taking highlighting
+ // and marked text into account.
+ function insertLineContent(line, builder, styles) {
+ var spans = line.markedSpans, allText = line.text, at = 0;
+ if (!spans) {
+ for (var i$1 = 1; i$1 < styles.length; i$1+=2)
+ { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); }
+ return
+ }
+
+ var len = allText.length, pos = 0, i = 1, text = "", style, css;
+ var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes;
+ for (;;) {
+ if (nextChange == pos) { // Update current marker set
+ spanStyle = spanEndStyle = spanStartStyle = css = "";
+ attributes = null;
+ collapsed = null; nextChange = Infinity;
+ var foundBookmarks = [], endStyles = (void 0);
+ for (var j = 0; j < spans.length; ++j) {
+ var sp = spans[j], m = sp.marker;
+ if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
+ foundBookmarks.push(m);
+ } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) {
+ if (sp.to != null && sp.to != pos && nextChange > sp.to) {
+ nextChange = sp.to;
+ spanEndStyle = "";
+ }
+ if (m.className) { spanStyle += " " + m.className; }
+ if (m.css) { css = (css ? css + ";" : "") + m.css; }
+ if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle; }
+ if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); }
+ // support for the old title property
+ // https://github.com/codemirror/CodeMirror/pull/5673
+ if (m.title) { (attributes || (attributes = {})).title = m.title; }
+ if (m.attributes) {
+ for (var attr in m.attributes)
+ { (attributes || (attributes = {}))[attr] = m.attributes[attr]; }
+ }
+ if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
+ { collapsed = sp; }
+ } else if (sp.from > pos && nextChange > sp.from) {
+ nextChange = sp.from;
+ }
+ }
+ if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2)
+ { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1]; } } }
+
+ if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2)
+ { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } }
+ if (collapsed && (collapsed.from || 0) == pos) {
+ buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
+ collapsed.marker, collapsed.from == null);
+ if (collapsed.to == null) { return }
+ if (collapsed.to == pos) { collapsed = false; }
+ }
+ }
+ if (pos >= len) { break }
+
+ var upto = Math.min(len, nextChange);
+ while (true) {
+ if (text) {
+ var end = pos + text.length;
+ if (!collapsed) {
+ var tokenText = end > upto ? text.slice(0, upto - pos) : text;
+ builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
+ spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", css, attributes);
+ }
+ if (end >= upto) {text = text.slice(upto - pos); pos = upto; break}
+ pos = end;
+ spanStartStyle = "";
+ }
+ text = allText.slice(at, at = styles[i++]);
+ style = interpretTokenStyle(styles[i++], builder.cm.options);
+ }
+ }
+ }
+
+
+ // These objects are used to represent the visible (currently drawn)
+ // part of the document. A LineView may correspond to multiple
+ // logical lines, if those are connected by collapsed ranges.
+ function LineView(doc, line, lineN) {
+ // The starting line
+ this.line = line;
+ // Continuing lines, if any
+ this.rest = visualLineContinued(line);
+ // Number of logical lines in this visual line
+ this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;
+ this.node = this.text = null;
+ this.hidden = lineIsHidden(doc, line);
+ }
+
+ // Create a range of LineView objects for the given lines.
+ function buildViewArray(cm, from, to) {
+ var array = [], nextPos;
+ for (var pos = from; pos < to; pos = nextPos) {
+ var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);
+ nextPos = pos + view.size;
+ array.push(view);
+ }
+ return array
+ }
+
+ var operationGroup = null;
+
+ function pushOperation(op) {
+ if (operationGroup) {
+ operationGroup.ops.push(op);
+ } else {
+ op.ownsGroup = operationGroup = {
+ ops: [op],
+ delayedCallbacks: []
+ };
+ }
+ }
+
+ function fireCallbacksForOps(group) {
+ // Calls delayed callbacks and cursorActivity handlers until no
+ // new ones appear
+ var callbacks = group.delayedCallbacks, i = 0;
+ do {
+ for (; i < callbacks.length; i++)
+ { callbacks[i].call(null); }
+ for (var j = 0; j < group.ops.length; j++) {
+ var op = group.ops[j];
+ if (op.cursorActivityHandlers)
+ { while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
+ { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } }
+ }
+ } while (i < callbacks.length)
+ }
+
+ function finishOperation(op, endCb) {
+ var group = op.ownsGroup;
+ if (!group) { return }
+
+ try { fireCallbacksForOps(group); }
+ finally {
+ operationGroup = null;
+ endCb(group);
+ }
+ }
+
+ var orphanDelayedCallbacks = null;
+
+ // Often, we want to signal events at a point where we are in the
+ // middle of some work, but don't want the handler to start calling
+ // other methods on the editor, which might be in an inconsistent
+ // state or simply not expect any other events to happen.
+ // signalLater looks whether there are any handlers, and schedules
+ // them to be executed when the last operation ends, or, if no
+ // operation is active, when a timeout fires.
+ function signalLater(emitter, type /*, values...*/) {
+ var arr = getHandlers(emitter, type);
+ if (!arr.length) { return }
+ var args = Array.prototype.slice.call(arguments, 2), list;
+ if (operationGroup) {
+ list = operationGroup.delayedCallbacks;
+ } else if (orphanDelayedCallbacks) {
+ list = orphanDelayedCallbacks;
+ } else {
+ list = orphanDelayedCallbacks = [];
+ setTimeout(fireOrphanDelayed, 0);
+ }
+ var loop = function ( i ) {
+ list.push(function () { return arr[i].apply(null, args); });
+ };
+
+ for (var i = 0; i < arr.length; ++i)
+ loop( i );
+ }
+
+ function fireOrphanDelayed() {
+ var delayed = orphanDelayedCallbacks;
+ orphanDelayedCallbacks = null;
+ for (var i = 0; i < delayed.length; ++i) { delayed[i](); }
+ }
+
+ // When an aspect of a line changes, a string is added to
+ // lineView.changes. This updates the relevant part of the line's
+ // DOM structure.
+ function updateLineForChanges(cm, lineView, lineN, dims) {
+ for (var j = 0; j < lineView.changes.length; j++) {
+ var type = lineView.changes[j];
+ if (type == "text") { updateLineText(cm, lineView); }
+ else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims); }
+ else if (type == "class") { updateLineClasses(cm, lineView); }
+ else if (type == "widget") { updateLineWidgets(cm, lineView, dims); }
+ }
+ lineView.changes = null;
+ }
+
+ // Lines with gutter elements, widgets or a background class need to
+ // be wrapped, and have the extra elements added to the wrapper div
+ function ensureLineWrapped(lineView) {
+ if (lineView.node == lineView.text) {
+ lineView.node = elt("div", null, null, "position: relative");
+ if (lineView.text.parentNode)
+ { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); }
+ lineView.node.appendChild(lineView.text);
+ if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; }
+ }
+ return lineView.node
+ }
+
+ function updateLineBackground(cm, lineView) {
+ var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass;
+ if (cls) { cls += " CodeMirror-linebackground"; }
+ if (lineView.background) {
+ if (cls) { lineView.background.className = cls; }
+ else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }
+ } else if (cls) {
+ var wrap = ensureLineWrapped(lineView);
+ lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild);
+ cm.display.input.setUneditable(lineView.background);
+ }
+ }
+
+ // Wrapper around buildLineContent which will reuse the structure
+ // in display.externalMeasured when possible.
+ function getLineContent(cm, lineView) {
+ var ext = cm.display.externalMeasured;
+ if (ext && ext.line == lineView.line) {
+ cm.display.externalMeasured = null;
+ lineView.measure = ext.measure;
+ return ext.built
+ }
+ return buildLineContent(cm, lineView)
+ }
+
+ // Redraw the line's text. Interacts with the background and text
+ // classes because the mode may output tokens that influence these
+ // classes.
+ function updateLineText(cm, lineView) {
+ var cls = lineView.text.className;
+ var built = getLineContent(cm, lineView);
+ if (lineView.text == lineView.node) { lineView.node = built.pre; }
+ lineView.text.parentNode.replaceChild(built.pre, lineView.text);
+ lineView.text = built.pre;
+ if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
+ lineView.bgClass = built.bgClass;
+ lineView.textClass = built.textClass;
+ updateLineClasses(cm, lineView);
+ } else if (cls) {
+ lineView.text.className = cls;
+ }
+ }
+
+ function updateLineClasses(cm, lineView) {
+ updateLineBackground(cm, lineView);
+ if (lineView.line.wrapClass)
+ { ensureLineWrapped(lineView).className = lineView.line.wrapClass; }
+ else if (lineView.node != lineView.text)
+ { lineView.node.className = ""; }
+ var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass;
+ lineView.text.className = textClass || "";
+ }
+
+ function updateLineGutter(cm, lineView, lineN, dims) {
+ if (lineView.gutter) {
+ lineView.node.removeChild(lineView.gutter);
+ lineView.gutter = null;
+ }
+ if (lineView.gutterBackground) {
+ lineView.node.removeChild(lineView.gutterBackground);
+ lineView.gutterBackground = null;
+ }
+ if (lineView.line.gutterClass) {
+ var wrap = ensureLineWrapped(lineView);
+ lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
+ ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px"));
+ cm.display.input.setUneditable(lineView.gutterBackground);
+ wrap.insertBefore(lineView.gutterBackground, lineView.text);
+ }
+ var markers = lineView.line.gutterMarkers;
+ if (cm.options.lineNumbers || markers) {
+ var wrap$1 = ensureLineWrapped(lineView);
+ var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"));
+ cm.display.input.setUneditable(gutterWrap);
+ wrap$1.insertBefore(gutterWrap, lineView.text);
+ if (lineView.line.gutterClass)
+ { gutterWrap.className += " " + lineView.line.gutterClass; }
+ if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
+ { lineView.lineNumber = gutterWrap.appendChild(
+ elt("div", lineNumberFor(cm.options, lineN),
+ "CodeMirror-linenumber CodeMirror-gutter-elt",
+ ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))); }
+ if (markers) { for (var k = 0; k < cm.display.gutterSpecs.length; ++k) {
+ var id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id];
+ if (found)
+ { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt",
+ ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))); }
+ } }
+ }
+ }
+
+ function updateLineWidgets(cm, lineView, dims) {
+ if (lineView.alignable) { lineView.alignable = null; }
+ for (var node = lineView.node.firstChild, next = (void 0); node; node = next) {
+ next = node.nextSibling;
+ if (node.className == "CodeMirror-linewidget")
+ { lineView.node.removeChild(node); }
+ }
+ insertLineWidgets(cm, lineView, dims);
+ }
+
+ // Build a line's DOM representation from scratch
+ function buildLineElement(cm, lineView, lineN, dims) {
+ var built = getLineContent(cm, lineView);
+ lineView.text = lineView.node = built.pre;
+ if (built.bgClass) { lineView.bgClass = built.bgClass; }
+ if (built.textClass) { lineView.textClass = built.textClass; }
+
+ updateLineClasses(cm, lineView);
+ updateLineGutter(cm, lineView, lineN, dims);
+ insertLineWidgets(cm, lineView, dims);
+ return lineView.node
+ }
+
+ // A lineView may contain multiple logical lines (when merged by
+ // collapsed spans). The widgets for all of them need to be drawn.
+ function insertLineWidgets(cm, lineView, dims) {
+ insertLineWidgetsFor(cm, lineView.line, lineView, dims, true);
+ if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)
+ { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } }
+ }
+
+ function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
+ if (!line.widgets) { return }
+ var wrap = ensureLineWrapped(lineView);
+ for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
+ var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
+ if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true"); }
+ positionLineWidget(widget, node, lineView, dims);
+ cm.display.input.setUneditable(node);
+ if (allowAbove && widget.above)
+ { wrap.insertBefore(node, lineView.gutter || lineView.text); }
+ else
+ { wrap.appendChild(node); }
+ signalLater(widget, "redraw");
+ }
+ }
+
+ function positionLineWidget(widget, node, lineView, dims) {
+ if (widget.noHScroll) {
+ (lineView.alignable || (lineView.alignable = [])).push(node);
+ var width = dims.wrapperWidth;
+ node.style.left = dims.fixedPos + "px";
+ if (!widget.coverGutter) {
+ width -= dims.gutterTotalWidth;
+ node.style.paddingLeft = dims.gutterTotalWidth + "px";
+ }
+ node.style.width = width + "px";
+ }
+ if (widget.coverGutter) {
+ node.style.zIndex = 5;
+ node.style.position = "relative";
+ if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px"; }
+ }
+ }
+
+ function widgetHeight(widget) {
+ if (widget.height != null) { return widget.height }
+ var cm = widget.doc.cm;
+ if (!cm) { return 0 }
+ if (!contains(document.body, widget.node)) {
+ var parentStyle = "position: relative;";
+ if (widget.coverGutter)
+ { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; }
+ if (widget.noHScroll)
+ { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; }
+ removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle));
+ }
+ return widget.height = widget.node.parentNode.offsetHeight
+ }
+
+ // Return true when the given mouse event happened in a widget
+ function eventInWidget(display, e) {
+ for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
+ if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") ||
+ (n.parentNode == display.sizer && n != display.mover))
+ { return true }
+ }
+ }
+
+ // POSITION MEASUREMENT
+
+ function paddingTop(display) {return display.lineSpace.offsetTop}
+ function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight}
+ function paddingH(display) {
+ if (display.cachedPaddingH) { return display.cachedPaddingH }
+ var e = removeChildrenAndAdd(display.measure, elt("pre", "x", "CodeMirror-line-like"));
+ var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
+ var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
+ if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; }
+ return data
+ }
+
+ function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth }
+ function displayWidth(cm) {
+ return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth
+ }
+ function displayHeight(cm) {
+ return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight
+ }
+
+ // Ensure the lineView.wrapping.heights array is populated. This is
+ // an array of bottom offsets for the lines that make up a drawn
+ // line. When lineWrapping is on, there might be more than one
+ // height.
+ function ensureLineHeights(cm, lineView, rect) {
+ var wrapping = cm.options.lineWrapping;
+ var curWidth = wrapping && displayWidth(cm);
+ if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
+ var heights = lineView.measure.heights = [];
+ if (wrapping) {
+ lineView.measure.width = curWidth;
+ var rects = lineView.text.firstChild.getClientRects();
+ for (var i = 0; i < rects.length - 1; i++) {
+ var cur = rects[i], next = rects[i + 1];
+ if (Math.abs(cur.bottom - next.bottom) > 2)
+ { heights.push((cur.bottom + next.top) / 2 - rect.top); }
+ }
+ }
+ heights.push(rect.bottom - rect.top);
+ }
+ }
+
+ // Find a line map (mapping character offsets to text nodes) and a
+ // measurement cache for the given line number. (A line view might
+ // contain multiple lines when collapsed ranges are present.)
+ function mapFromLineView(lineView, line, lineN) {
+ if (lineView.line == line)
+ { return {map: lineView.measure.map, cache: lineView.measure.cache} }
+ for (var i = 0; i < lineView.rest.length; i++)
+ { if (lineView.rest[i] == line)
+ { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } }
+ for (var i$1 = 0; i$1 < lineView.rest.length; i$1++)
+ { if (lineNo(lineView.rest[i$1]) > lineN)
+ { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } }
+ }
+
+ // Render a line into the hidden node display.externalMeasured. Used
+ // when measurement is needed for a line that's not in the viewport.
+ function updateExternalMeasurement(cm, line) {
+ line = visualLine(line);
+ var lineN = lineNo(line);
+ var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);
+ view.lineN = lineN;
+ var built = view.built = buildLineContent(cm, view);
+ view.text = built.pre;
+ removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
+ return view
+ }
+
+ // Get a {top, bottom, left, right} box (in line-local coordinates)
+ // for a given character.
+ function measureChar(cm, line, ch, bias) {
+ return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias)
+ }
+
+ // Find a line view that corresponds to the given line number.
+ function findViewForLine(cm, lineN) {
+ if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
+ { return cm.display.view[findViewIndex(cm, lineN)] }
+ var ext = cm.display.externalMeasured;
+ if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
+ { return ext }
+ }
+
+ // Measurement can be split in two steps, the set-up work that
+ // applies to the whole line, and the measurement of the actual
+ // character. Functions like coordsChar, that need to do a lot of
+ // measurements in a row, can thus ensure that the set-up work is
+ // only done once.
+ function prepareMeasureForLine(cm, line) {
+ var lineN = lineNo(line);
+ var view = findViewForLine(cm, lineN);
+ if (view && !view.text) {
+ view = null;
+ } else if (view && view.changes) {
+ updateLineForChanges(cm, view, lineN, getDimensions(cm));
+ cm.curOp.forceUpdate = true;
+ }
+ if (!view)
+ { view = updateExternalMeasurement(cm, line); }
+
+ var info = mapFromLineView(view, line, lineN);
+ return {
+ line: line, view: view, rect: null,
+ map: info.map, cache: info.cache, before: info.before,
+ hasHeights: false
+ }
+ }
+
+ // Given a prepared measurement object, measures the position of an
+ // actual character (or fetches it from the cache).
+ function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
+ if (prepared.before) { ch = -1; }
+ var key = ch + (bias || ""), found;
+ if (prepared.cache.hasOwnProperty(key)) {
+ found = prepared.cache[key];
+ } else {
+ if (!prepared.rect)
+ { prepared.rect = prepared.view.text.getBoundingClientRect(); }
+ if (!prepared.hasHeights) {
+ ensureLineHeights(cm, prepared.view, prepared.rect);
+ prepared.hasHeights = true;
+ }
+ found = measureCharInner(cm, prepared, ch, bias);
+ if (!found.bogus) { prepared.cache[key] = found; }
+ }
+ return {left: found.left, right: found.right,
+ top: varHeight ? found.rtop : found.top,
+ bottom: varHeight ? found.rbottom : found.bottom}
+ }
+
+ var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
+
+ function nodeAndOffsetInLineMap(map$$1, ch, bias) {
+ var node, start, end, collapse, mStart, mEnd;
+ // First, search the line map for the text node corresponding to,
+ // or closest to, the target character.
+ for (var i = 0; i < map$$1.length; i += 3) {
+ mStart = map$$1[i];
+ mEnd = map$$1[i + 1];
+ if (ch < mStart) {
+ start = 0; end = 1;
+ collapse = "left";
+ } else if (ch < mEnd) {
+ start = ch - mStart;
+ end = start + 1;
+ } else if (i == map$$1.length - 3 || ch == mEnd && map$$1[i + 3] > ch) {
+ end = mEnd - mStart;
+ start = end - 1;
+ if (ch >= mEnd) { collapse = "right"; }
+ }
+ if (start != null) {
+ node = map$$1[i + 2];
+ if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
+ { collapse = bias; }
+ if (bias == "left" && start == 0)
+ { while (i && map$$1[i - 2] == map$$1[i - 3] && map$$1[i - 1].insertLeft) {
+ node = map$$1[(i -= 3) + 2];
+ collapse = "left";
+ } }
+ if (bias == "right" && start == mEnd - mStart)
+ { while (i < map$$1.length - 3 && map$$1[i + 3] == map$$1[i + 4] && !map$$1[i + 5].insertLeft) {
+ node = map$$1[(i += 3) + 2];
+ collapse = "right";
+ } }
+ break
+ }
+ }
+ return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}
+ }
+
+ function getUsefulRect(rects, bias) {
+ var rect = nullRect;
+ if (bias == "left") { for (var i = 0; i < rects.length; i++) {
+ if ((rect = rects[i]).left != rect.right) { break }
+ } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) {
+ if ((rect = rects[i$1]).left != rect.right) { break }
+ } }
+ return rect
+ }
+
+ function measureCharInner(cm, prepared, ch, bias) {
+ var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);
+ var node = place.node, start = place.start, end = place.end, collapse = place.collapse;
+
+ var rect;
+ if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
+ for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned
+ while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; }
+ while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; }
+ if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart)
+ { rect = node.parentNode.getBoundingClientRect(); }
+ else
+ { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); }
+ if (rect.left || rect.right || start == 0) { break }
+ end = start;
+ start = start - 1;
+ collapse = "right";
+ }
+ if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); }
+ } else { // If it is a widget, simply get the box for the whole widget.
+ if (start > 0) { collapse = bias = "right"; }
+ var rects;
+ if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
+ { rect = rects[bias == "right" ? rects.length - 1 : 0]; }
+ else
+ { rect = node.getBoundingClientRect(); }
+ }
+ if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
+ var rSpan = node.parentNode.getClientRects()[0];
+ if (rSpan)
+ { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; }
+ else
+ { rect = nullRect; }
+ }
+
+ var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;
+ var mid = (rtop + rbot) / 2;
+ var heights = prepared.view.measure.heights;
+ var i = 0;
+ for (; i < heights.length - 1; i++)
+ { if (mid < heights[i]) { break } }
+ var top = i ? heights[i - 1] : 0, bot = heights[i];
+ var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
+ right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
+ top: top, bottom: bot};
+ if (!rect.left && !rect.right) { result.bogus = true; }
+ if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }
+
+ return result
+ }
+
+ // Work around problem with bounding client rects on ranges being
+ // returned incorrectly when zoomed on IE10 and below.
+ function maybeUpdateRectForZooming(measure, rect) {
+ if (!window.screen || screen.logicalXDPI == null ||
+ screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
+ { return rect }
+ var scaleX = screen.logicalXDPI / screen.deviceXDPI;
+ var scaleY = screen.logicalYDPI / screen.deviceYDPI;
+ return {left: rect.left * scaleX, right: rect.right * scaleX,
+ top: rect.top * scaleY, bottom: rect.bottom * scaleY}
+ }
+
+ function clearLineMeasurementCacheFor(lineView) {
+ if (lineView.measure) {
+ lineView.measure.cache = {};
+ lineView.measure.heights = null;
+ if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)
+ { lineView.measure.caches[i] = {}; } }
+ }
+ }
+
+ function clearLineMeasurementCache(cm) {
+ cm.display.externalMeasure = null;
+ removeChildren(cm.display.lineMeasure);
+ for (var i = 0; i < cm.display.view.length; i++)
+ { clearLineMeasurementCacheFor(cm.display.view[i]); }
+ }
+
+ function clearCaches(cm) {
+ clearLineMeasurementCache(cm);
+ cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;
+ if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; }
+ cm.display.lineNumChars = null;
+ }
+
+ function pageScrollX() {
+ // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206
+ // which causes page_Offset and bounding client rects to use
+ // different reference viewports and invalidate our calculations.
+ if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) }
+ return window.pageXOffset || (document.documentElement || document.body).scrollLeft
+ }
+ function pageScrollY() {
+ if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) }
+ return window.pageYOffset || (document.documentElement || document.body).scrollTop
+ }
+
+ function widgetTopHeight(lineObj) {
+ var height = 0;
+ if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above)
+ { height += widgetHeight(lineObj.widgets[i]); } } }
+ return height
+ }
+
+ // Converts a {top, bottom, left, right} box from line-local
+ // coordinates into another coordinate system. Context may be one of
+ // "line", "div" (display.lineDiv), "local"./null (editor), "window",
+ // or "page".
+ function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) {
+ if (!includeWidgets) {
+ var height = widgetTopHeight(lineObj);
+ rect.top += height; rect.bottom += height;
+ }
+ if (context == "line") { return rect }
+ if (!context) { context = "local"; }
+ var yOff = heightAtLine(lineObj);
+ if (context == "local") { yOff += paddingTop(cm.display); }
+ else { yOff -= cm.display.viewOffset; }
+ if (context == "page" || context == "window") {
+ var lOff = cm.display.lineSpace.getBoundingClientRect();
+ yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
+ var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
+ rect.left += xOff; rect.right += xOff;
+ }
+ rect.top += yOff; rect.bottom += yOff;
+ return rect
+ }
+
+ // Coverts a box from "div" coords to another coordinate system.
+ // Context may be "window", "page", "div", or "local"./null.
+ function fromCoordSystem(cm, coords, context) {
+ if (context == "div") { return coords }
+ var left = coords.left, top = coords.top;
+ // First move into "page" coordinate system
+ if (context == "page") {
+ left -= pageScrollX();
+ top -= pageScrollY();
+ } else if (context == "local" || !context) {
+ var localBox = cm.display.sizer.getBoundingClientRect();
+ left += localBox.left;
+ top += localBox.top;
+ }
+
+ var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
+ return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}
+ }
+
+ function charCoords(cm, pos, context, lineObj, bias) {
+ if (!lineObj) { lineObj = getLine(cm.doc, pos.line); }
+ return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context)
+ }
+
+ // Returns a box for a given cursor position, which may have an
+ // 'other' property containing the position of the secondary cursor
+ // on a bidi boundary.
+ // A cursor Pos(line, char, "before") is on the same visual line as `char - 1`
+ // and after `char - 1` in writing order of `char - 1`
+ // A cursor Pos(line, char, "after") is on the same visual line as `char`
+ // and before `char` in writing order of `char`
+ // Examples (upper-case letters are RTL, lower-case are LTR):
+ // Pos(0, 1, ...)
+ // before after
+ // ab a|b a|b
+ // aB a|B aB|
+ // Ab |Ab A|b
+ // AB B|A B|A
+ // Every position after the last character on a line is considered to stick
+ // to the last character on the line.
+ function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
+ lineObj = lineObj || getLine(cm.doc, pos.line);
+ if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }
+ function get(ch, right) {
+ var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight);
+ if (right) { m.left = m.right; } else { m.right = m.left; }
+ return intoCoordSystem(cm, lineObj, m, context)
+ }
+ var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky;
+ if (ch >= lineObj.text.length) {
+ ch = lineObj.text.length;
+ sticky = "before";
+ } else if (ch <= 0) {
+ ch = 0;
+ sticky = "after";
+ }
+ if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") }
+
+ function getBidi(ch, partPos, invert) {
+ var part = order[partPos], right = part.level == 1;
+ return get(invert ? ch - 1 : ch, right != invert)
+ }
+ var partPos = getBidiPartAt(order, ch, sticky);
+ var other = bidiOther;
+ var val = getBidi(ch, partPos, sticky == "before");
+ if (other != null) { val.other = getBidi(ch, other, sticky != "before"); }
+ return val
+ }
+
+ // Used to cheaply estimate the coordinates for a position. Used for
+ // intermediate scroll updates.
+ function estimateCoords(cm, pos) {
+ var left = 0;
+ pos = clipPos(cm.doc, pos);
+ if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; }
+ var lineObj = getLine(cm.doc, pos.line);
+ var top = heightAtLine(lineObj) + paddingTop(cm.display);
+ return {left: left, right: left, top: top, bottom: top + lineObj.height}
+ }
+
+ // Positions returned by coordsChar contain some extra information.
+ // xRel is the relative x position of the input coordinates compared
+ // to the found position (so xRel > 0 means the coordinates are to
+ // the right of the character position, for example). When outside
+ // is true, that means the coordinates lie outside the line's
+ // vertical range.
+ function PosWithInfo(line, ch, sticky, outside, xRel) {
+ var pos = Pos(line, ch, sticky);
+ pos.xRel = xRel;
+ if (outside) { pos.outside = outside; }
+ return pos
+ }
+
+ // Compute the character position closest to the given coordinates.
+ // Input must be lineSpace-local ("div" coordinate system).
+ function coordsChar(cm, x, y) {
+ var doc = cm.doc;
+ y += cm.display.viewOffset;
+ if (y < 0) { return PosWithInfo(doc.first, 0, null, -1, -1) }
+ var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
+ if (lineN > last)
+ { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 1) }
+ if (x < 0) { x = 0; }
+
+ var lineObj = getLine(doc, lineN);
+ for (;;) {
+ var found = coordsCharInner(cm, lineObj, lineN, x, y);
+ var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0));
+ if (!collapsed) { return found }
+ var rangeEnd = collapsed.find(1);
+ if (rangeEnd.line == lineN) { return rangeEnd }
+ lineObj = getLine(doc, lineN = rangeEnd.line);
+ }
+ }
+
+ function wrappedLineExtent(cm, lineObj, preparedMeasure, y) {
+ y -= widgetTopHeight(lineObj);
+ var end = lineObj.text.length;
+ var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0);
+ end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end);
+ return {begin: begin, end: end}
+ }
+
+ function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) {
+ if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }
+ var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top;
+ return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop)
+ }
+
+ // Returns true if the given side of a box is after the given
+ // coordinates, in top-to-bottom, left-to-right order.
+ function boxIsAfter(box, x, y, left) {
+ return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x
+ }
+
+ function coordsCharInner(cm, lineObj, lineNo$$1, x, y) {
+ // Move y into line-local coordinate space
+ y -= heightAtLine(lineObj);
+ var preparedMeasure = prepareMeasureForLine(cm, lineObj);
+ // When directly calling `measureCharPrepared`, we have to adjust
+ // for the widgets at this line.
+ var widgetHeight$$1 = widgetTopHeight(lineObj);
+ var begin = 0, end = lineObj.text.length, ltr = true;
+
+ var order = getOrder(lineObj, cm.doc.direction);
+ // If the line isn't plain left-to-right text, first figure out
+ // which bidi section the coordinates fall into.
+ if (order) {
+ var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart)
+ (cm, lineObj, lineNo$$1, preparedMeasure, order, x, y);
+ ltr = part.level != 1;
+ // The awkward -1 offsets are needed because findFirst (called
+ // on these below) will treat its first bound as inclusive,
+ // second as exclusive, but we want to actually address the
+ // characters in the part's range
+ begin = ltr ? part.from : part.to - 1;
+ end = ltr ? part.to : part.from - 1;
+ }
+
+ // A binary search to find the first character whose bounding box
+ // starts after the coordinates. If we run across any whose box wrap
+ // the coordinates, store that.
+ var chAround = null, boxAround = null;
+ var ch = findFirst(function (ch) {
+ var box = measureCharPrepared(cm, preparedMeasure, ch);
+ box.top += widgetHeight$$1; box.bottom += widgetHeight$$1;
+ if (!boxIsAfter(box, x, y, false)) { return false }
+ if (box.top <= y && box.left <= x) {
+ chAround = ch;
+ boxAround = box;
+ }
+ return true
+ }, begin, end);
+
+ var baseX, sticky, outside = false;
+ // If a box around the coordinates was found, use that
+ if (boxAround) {
+ // Distinguish coordinates nearer to the left or right side of the box
+ var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr;
+ ch = chAround + (atStart ? 0 : 1);
+ sticky = atStart ? "after" : "before";
+ baseX = atLeft ? boxAround.left : boxAround.right;
+ } else {
+ // (Adjust for extended bound, if necessary.)
+ if (!ltr && (ch == end || ch == begin)) { ch++; }
+ // To determine which side to associate with, get the box to the
+ // left of the character and compare it's vertical position to the
+ // coordinates
+ sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" :
+ (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight$$1 <= y) == ltr ?
+ "after" : "before";
+ // Now get accurate coordinates for this place, in order to get a
+ // base X position
+ var coords = cursorCoords(cm, Pos(lineNo$$1, ch, sticky), "line", lineObj, preparedMeasure);
+ baseX = coords.left;
+ outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0;
+ }
+
+ ch = skipExtendingChars(lineObj.text, ch, 1);
+ return PosWithInfo(lineNo$$1, ch, sticky, outside, x - baseX)
+ }
+
+ function coordsBidiPart(cm, lineObj, lineNo$$1, preparedMeasure, order, x, y) {
+ // Bidi parts are sorted left-to-right, and in a non-line-wrapping
+ // situation, we can take this ordering to correspond to the visual
+ // ordering. This finds the first part whose end is after the given
+ // coordinates.
+ var index = findFirst(function (i) {
+ var part = order[i], ltr = part.level != 1;
+ return boxIsAfter(cursorCoords(cm, Pos(lineNo$$1, ltr ? part.to : part.from, ltr ? "before" : "after"),
+ "line", lineObj, preparedMeasure), x, y, true)
+ }, 0, order.length - 1);
+ var part = order[index];
+ // If this isn't the first part, the part's start is also after
+ // the coordinates, and the coordinates aren't on the same line as
+ // that start, move one part back.
+ if (index > 0) {
+ var ltr = part.level != 1;
+ var start = cursorCoords(cm, Pos(lineNo$$1, ltr ? part.from : part.to, ltr ? "after" : "before"),
+ "line", lineObj, preparedMeasure);
+ if (boxIsAfter(start, x, y, true) && start.top > y)
+ { part = order[index - 1]; }
+ }
+ return part
+ }
+
+ function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) {
+ // In a wrapped line, rtl text on wrapping boundaries can do things
+ // that don't correspond to the ordering in our `order` array at
+ // all, so a binary search doesn't work, and we want to return a
+ // part that only spans one line so that the binary search in
+ // coordsCharInner is safe. As such, we first find the extent of the
+ // wrapped line, and then do a flat search in which we discard any
+ // spans that aren't on the line.
+ var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y);
+ var begin = ref.begin;
+ var end = ref.end;
+ if (/\s/.test(lineObj.text.charAt(end - 1))) { end--; }
+ var part = null, closestDist = null;
+ for (var i = 0; i < order.length; i++) {
+ var p = order[i];
+ if (p.from >= end || p.to <= begin) { continue }
+ var ltr = p.level != 1;
+ var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right;
+ // Weigh against spans ending before this, so that they are only
+ // picked if nothing ends after
+ var dist = endX < x ? x - endX + 1e9 : endX - x;
+ if (!part || closestDist > dist) {
+ part = p;
+ closestDist = dist;
+ }
+ }
+ if (!part) { part = order[order.length - 1]; }
+ // Clip the part to the wrapped line.
+ if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; }
+ if (part.to > end) { part = {from: part.from, to: end, level: part.level}; }
+ return part
+ }
+
+ var measureText;
+ // Compute the default text height.
+ function textHeight(display) {
+ if (display.cachedTextHeight != null) { return display.cachedTextHeight }
+ if (measureText == null) {
+ measureText = elt("pre", null, "CodeMirror-line-like");
+ // Measure a bunch of lines, for browsers that compute
+ // fractional heights.
+ for (var i = 0; i < 49; ++i) {
+ measureText.appendChild(document.createTextNode("x"));
+ measureText.appendChild(elt("br"));
+ }
+ measureText.appendChild(document.createTextNode("x"));
+ }
+ removeChildrenAndAdd(display.measure, measureText);
+ var height = measureText.offsetHeight / 50;
+ if (height > 3) { display.cachedTextHeight = height; }
+ removeChildren(display.measure);
+ return height || 1
+ }
+
+ // Compute the default character width.
+ function charWidth(display) {
+ if (display.cachedCharWidth != null) { return display.cachedCharWidth }
+ var anchor = elt("span", "xxxxxxxxxx");
+ var pre = elt("pre", [anchor], "CodeMirror-line-like");
+ removeChildrenAndAdd(display.measure, pre);
+ var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
+ if (width > 2) { display.cachedCharWidth = width; }
+ return width || 10
+ }
+
+ // Do a bulk-read of the DOM positions and sizes needed to draw the
+ // view, so that we don't interleave reading and writing to the DOM.
+ function getDimensions(cm) {
+ var d = cm.display, left = {}, width = {};
+ var gutterLeft = d.gutters.clientLeft;
+ for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
+ var id = cm.display.gutterSpecs[i].className;
+ left[id] = n.offsetLeft + n.clientLeft + gutterLeft;
+ width[id] = n.clientWidth;
+ }
+ return {fixedPos: compensateForHScroll(d),
+ gutterTotalWidth: d.gutters.offsetWidth,
+ gutterLeft: left,
+ gutterWidth: width,
+ wrapperWidth: d.wrapper.clientWidth}
+ }
+
+ // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
+ // but using getBoundingClientRect to get a sub-pixel-accurate
+ // result.
+ function compensateForHScroll(display) {
+ return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left
+ }
+
+ // Returns a function that estimates the height of a line, to use as
+ // first approximation until the line becomes visible (and is thus
+ // properly measurable).
+ function estimateHeight(cm) {
+ var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
+ var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
+ return function (line) {
+ if (lineIsHidden(cm.doc, line)) { return 0 }
+
+ var widgetsHeight = 0;
+ if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) {
+ if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; }
+ } }
+
+ if (wrapping)
+ { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th }
+ else
+ { return widgetsHeight + th }
+ }
+ }
+
+ function estimateLineHeights(cm) {
+ var doc = cm.doc, est = estimateHeight(cm);
+ doc.iter(function (line) {
+ var estHeight = est(line);
+ if (estHeight != line.height) { updateLineHeight(line, estHeight); }
+ });
+ }
+
+ // Given a mouse event, find the corresponding position. If liberal
+ // is false, it checks whether a gutter or scrollbar was clicked,
+ // and returns null if it was. forRect is used by rectangular
+ // selections, and tries to estimate a character position even for
+ // coordinates beyond the right of the text.
+ function posFromMouse(cm, e, liberal, forRect) {
+ var display = cm.display;
+ if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null }
+
+ var x, y, space = display.lineSpace.getBoundingClientRect();
+ // Fails unpredictably on IE[67] when mouse is dragged around quickly.
+ try { x = e.clientX - space.left; y = e.clientY - space.top; }
+ catch (e) { return null }
+ var coords = coordsChar(cm, x, y), line;
+ if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
+ var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
+ coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
+ }
+ return coords
+ }
+
+ // Find the view element corresponding to a given line. Return null
+ // when the line isn't visible.
+ function findViewIndex(cm, n) {
+ if (n >= cm.display.viewTo) { return null }
+ n -= cm.display.viewFrom;
+ if (n < 0) { return null }
+ var view = cm.display.view;
+ for (var i = 0; i < view.length; i++) {
+ n -= view[i].size;
+ if (n < 0) { return i }
+ }
+ }
+
+ // Updates the display.view data structure for a given change to the
+ // document. From and to are in pre-change coordinates. Lendiff is
+ // the amount of lines added or subtracted by the change. This is
+ // used for changes that span multiple lines, or change the way
+ // lines are divided into visual lines. regLineChange (below)
+ // registers single-line changes.
+ function regChange(cm, from, to, lendiff) {
+ if (from == null) { from = cm.doc.first; }
+ if (to == null) { to = cm.doc.first + cm.doc.size; }
+ if (!lendiff) { lendiff = 0; }
+
+ var display = cm.display;
+ if (lendiff && to < display.viewTo &&
+ (display.updateLineNumbers == null || display.updateLineNumbers > from))
+ { display.updateLineNumbers = from; }
+
+ cm.curOp.viewChanged = true;
+
+ if (from >= display.viewTo) { // Change after
+ if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
+ { resetView(cm); }
+ } else if (to <= display.viewFrom) { // Change before
+ if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
+ resetView(cm);
+ } else {
+ display.viewFrom += lendiff;
+ display.viewTo += lendiff;
+ }
+ } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
+ resetView(cm);
+ } else if (from <= display.viewFrom) { // Top overlap
+ var cut = viewCuttingPoint(cm, to, to + lendiff, 1);
+ if (cut) {
+ display.view = display.view.slice(cut.index);
+ display.viewFrom = cut.lineN;
+ display.viewTo += lendiff;
+ } else {
+ resetView(cm);
+ }
+ } else if (to >= display.viewTo) { // Bottom overlap
+ var cut$1 = viewCuttingPoint(cm, from, from, -1);
+ if (cut$1) {
+ display.view = display.view.slice(0, cut$1.index);
+ display.viewTo = cut$1.lineN;
+ } else {
+ resetView(cm);
+ }
+ } else { // Gap in the middle
+ var cutTop = viewCuttingPoint(cm, from, from, -1);
+ var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);
+ if (cutTop && cutBot) {
+ display.view = display.view.slice(0, cutTop.index)
+ .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
+ .concat(display.view.slice(cutBot.index));
+ display.viewTo += lendiff;
+ } else {
+ resetView(cm);
+ }
+ }
+
+ var ext = display.externalMeasured;
+ if (ext) {
+ if (to < ext.lineN)
+ { ext.lineN += lendiff; }
+ else if (from < ext.lineN + ext.size)
+ { display.externalMeasured = null; }
+ }
+ }
+
+ // Register a change to a single line. Type must be one of "text",
+ // "gutter", "class", "widget"
+ function regLineChange(cm, line, type) {
+ cm.curOp.viewChanged = true;
+ var display = cm.display, ext = cm.display.externalMeasured;
+ if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
+ { display.externalMeasured = null; }
+
+ if (line < display.viewFrom || line >= display.viewTo) { return }
+ var lineView = display.view[findViewIndex(cm, line)];
+ if (lineView.node == null) { return }
+ var arr = lineView.changes || (lineView.changes = []);
+ if (indexOf(arr, type) == -1) { arr.push(type); }
+ }
+
+ // Clear the view.
+ function resetView(cm) {
+ cm.display.viewFrom = cm.display.viewTo = cm.doc.first;
+ cm.display.view = [];
+ cm.display.viewOffset = 0;
+ }
+
+ function viewCuttingPoint(cm, oldN, newN, dir) {
+ var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
+ if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
+ { return {index: index, lineN: newN} }
+ var n = cm.display.viewFrom;
+ for (var i = 0; i < index; i++)
+ { n += view[i].size; }
+ if (n != oldN) {
+ if (dir > 0) {
+ if (index == view.length - 1) { return null }
+ diff = (n + view[index].size) - oldN;
+ index++;
+ } else {
+ diff = n - oldN;
+ }
+ oldN += diff; newN += diff;
+ }
+ while (visualLineNo(cm.doc, newN) != newN) {
+ if (index == (dir < 0 ? 0 : view.length - 1)) { return null }
+ newN += dir * view[index - (dir < 0 ? 1 : 0)].size;
+ index += dir;
+ }
+ return {index: index, lineN: newN}
+ }
+
+ // Force the view to cover a given range, adding empty view element
+ // or clipping off existing ones as needed.
+ function adjustView(cm, from, to) {
+ var display = cm.display, view = display.view;
+ if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
+ display.view = buildViewArray(cm, from, to);
+ display.viewFrom = from;
+ } else {
+ if (display.viewFrom > from)
+ { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); }
+ else if (display.viewFrom < from)
+ { display.view = display.view.slice(findViewIndex(cm, from)); }
+ display.viewFrom = from;
+ if (display.viewTo < to)
+ { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); }
+ else if (display.viewTo > to)
+ { display.view = display.view.slice(0, findViewIndex(cm, to)); }
+ }
+ display.viewTo = to;
+ }
+
+ // Count the number of lines in the view whose DOM representation is
+ // out of date (or nonexistent).
+ function countDirtyView(cm) {
+ var view = cm.display.view, dirty = 0;
+ for (var i = 0; i < view.length; i++) {
+ var lineView = view[i];
+ if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; }
+ }
+ return dirty
+ }
+
+ function updateSelection(cm) {
+ cm.display.input.showSelection(cm.display.input.prepareSelection());
+ }
+
+ function prepareSelection(cm, primary) {
+ if ( primary === void 0 ) primary = true;
+
+ var doc = cm.doc, result = {};
+ var curFragment = result.cursors = document.createDocumentFragment();
+ var selFragment = result.selection = document.createDocumentFragment();
+
+ for (var i = 0; i < doc.sel.ranges.length; i++) {
+ if (!primary && i == doc.sel.primIndex) { continue }
+ var range$$1 = doc.sel.ranges[i];
+ if (range$$1.from().line >= cm.display.viewTo || range$$1.to().line < cm.display.viewFrom) { continue }
+ var collapsed = range$$1.empty();
+ if (collapsed || cm.options.showCursorWhenSelecting)
+ { drawSelectionCursor(cm, range$$1.head, curFragment); }
+ if (!collapsed)
+ { drawSelectionRange(cm, range$$1, selFragment); }
+ }
+ return result
+ }
+
+ // Draws a cursor for the given range
+ function drawSelectionCursor(cm, head, output) {
+ var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine);
+
+ var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
+ cursor.style.left = pos.left + "px";
+ cursor.style.top = pos.top + "px";
+ cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
+
+ if (pos.other) {
+ // Secondary cursor, shown when on a 'jump' in bi-directional text
+ var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
+ otherCursor.style.display = "";
+ otherCursor.style.left = pos.other.left + "px";
+ otherCursor.style.top = pos.other.top + "px";
+ otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
+ }
+ }
+
+ function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }
+
+ // Draws the given range as a highlighted selection
+ function drawSelectionRange(cm, range$$1, output) {
+ var display = cm.display, doc = cm.doc;
+ var fragment = document.createDocumentFragment();
+ var padding = paddingH(cm.display), leftSide = padding.left;
+ var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right;
+ var docLTR = doc.direction == "ltr";
+
+ function add(left, top, width, bottom) {
+ if (top < 0) { top = 0; }
+ top = Math.round(top);
+ bottom = Math.round(bottom);
+ fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px")));
+ }
+
+ function drawForLine(line, fromArg, toArg) {
+ var lineObj = getLine(doc, line);
+ var lineLen = lineObj.text.length;
+ var start, end;
+ function coords(ch, bias) {
+ return charCoords(cm, Pos(line, ch), "div", lineObj, bias)
+ }
+
+ function wrapX(pos, dir, side) {
+ var extent = wrappedLineExtentChar(cm, lineObj, null, pos);
+ var prop = (dir == "ltr") == (side == "after") ? "left" : "right";
+ var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1);
+ return coords(ch, prop)[prop]
+ }
+
+ var order = getOrder(lineObj, doc.direction);
+ iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) {
+ var ltr = dir == "ltr";
+ var fromPos = coords(from, ltr ? "left" : "right");
+ var toPos = coords(to - 1, ltr ? "right" : "left");
+
+ var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen;
+ var first = i == 0, last = !order || i == order.length - 1;
+ if (toPos.top - fromPos.top <= 3) { // Single line
+ var openLeft = (docLTR ? openStart : openEnd) && first;
+ var openRight = (docLTR ? openEnd : openStart) && last;
+ var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left;
+ var right = openRight ? rightSide : (ltr ? toPos : fromPos).right;
+ add(left, fromPos.top, right - left, fromPos.bottom);
+ } else { // Multiple lines
+ var topLeft, topRight, botLeft, botRight;
+ if (ltr) {
+ topLeft = docLTR && openStart && first ? leftSide : fromPos.left;
+ topRight = docLTR ? rightSide : wrapX(from, dir, "before");
+ botLeft = docLTR ? leftSide : wrapX(to, dir, "after");
+ botRight = docLTR && openEnd && last ? rightSide : toPos.right;
+ } else {
+ topLeft = !docLTR ? leftSide : wrapX(from, dir, "before");
+ topRight = !docLTR && openStart && first ? rightSide : fromPos.right;
+ botLeft = !docLTR && openEnd && last ? leftSide : toPos.left;
+ botRight = !docLTR ? rightSide : wrapX(to, dir, "after");
+ }
+ add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom);
+ if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); }
+ add(botLeft, toPos.top, botRight - botLeft, toPos.bottom);
+ }
+
+ if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; }
+ if (cmpCoords(toPos, start) < 0) { start = toPos; }
+ if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; }
+ if (cmpCoords(toPos, end) < 0) { end = toPos; }
+ });
+ return {start: start, end: end}
+ }
+
+ var sFrom = range$$1.from(), sTo = range$$1.to();
+ if (sFrom.line == sTo.line) {
+ drawForLine(sFrom.line, sFrom.ch, sTo.ch);
+ } else {
+ var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);
+ var singleVLine = visualLine(fromLine) == visualLine(toLine);
+ var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;
+ var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;
+ if (singleVLine) {
+ if (leftEnd.top < rightStart.top - 2) {
+ add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
+ add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);
+ } else {
+ add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
+ }
+ }
+ if (leftEnd.bottom < rightStart.top)
+ { add(leftSide, leftEnd.bottom, null, rightStart.top); }
+ }
+
+ output.appendChild(fragment);
+ }
+
+ // Cursor-blinking
+ function restartBlink(cm) {
+ if (!cm.state.focused) { return }
+ var display = cm.display;
+ clearInterval(display.blinker);
+ var on = true;
+ display.cursorDiv.style.visibility = "";
+ if (cm.options.cursorBlinkRate > 0)
+ { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; },
+ cm.options.cursorBlinkRate); }
+ else if (cm.options.cursorBlinkRate < 0)
+ { display.cursorDiv.style.visibility = "hidden"; }
+ }
+
+ function ensureFocus(cm) {
+ if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
+ }
+
+ function delayBlurEvent(cm) {
+ cm.state.delayingBlurEvent = true;
+ setTimeout(function () { if (cm.state.delayingBlurEvent) {
+ cm.state.delayingBlurEvent = false;
+ onBlur(cm);
+ } }, 100);
+ }
+
+ function onFocus(cm, e) {
+ if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false; }
+
+ if (cm.options.readOnly == "nocursor") { return }
+ if (!cm.state.focused) {
+ signal(cm, "focus", cm, e);
+ cm.state.focused = true;
+ addClass(cm.display.wrapper, "CodeMirror-focused");
+ // This test prevents this from firing when a context
+ // menu is closed (since the input reset would kill the
+ // select-all detection hack)
+ if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
+ cm.display.input.reset();
+ if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730
+ }
+ cm.display.input.receivedFocus();
+ }
+ restartBlink(cm);
+ }
+ function onBlur(cm, e) {
+ if (cm.state.delayingBlurEvent) { return }
+
+ if (cm.state.focused) {
+ signal(cm, "blur", cm, e);
+ cm.state.focused = false;
+ rmClass(cm.display.wrapper, "CodeMirror-focused");
+ }
+ clearInterval(cm.display.blinker);
+ setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150);
+ }
+
+ // Read the actual heights of the rendered lines, and update their
+ // stored heights to match.
+ function updateHeightsInViewport(cm) {
+ var display = cm.display;
+ var prevBottom = display.lineDiv.offsetTop;
+ for (var i = 0; i < display.view.length; i++) {
+ var cur = display.view[i], wrapping = cm.options.lineWrapping;
+ var height = (void 0), width = 0;
+ if (cur.hidden) { continue }
+ if (ie && ie_version < 8) {
+ var bot = cur.node.offsetTop + cur.node.offsetHeight;
+ height = bot - prevBottom;
+ prevBottom = bot;
+ } else {
+ var box = cur.node.getBoundingClientRect();
+ height = box.bottom - box.top;
+ // Check that lines don't extend past the right of the current
+ // editor width
+ if (!wrapping && cur.text.firstChild)
+ { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; }
+ }
+ var diff = cur.line.height - height;
+ if (diff > .005 || diff < -.005) {
+ updateLineHeight(cur.line, height);
+ updateWidgetHeight(cur.line);
+ if (cur.rest) { for (var j = 0; j < cur.rest.length; j++)
+ { updateWidgetHeight(cur.rest[j]); } }
+ }
+ if (width > cm.display.sizerWidth) {
+ var chWidth = Math.ceil(width / charWidth(cm.display));
+ if (chWidth > cm.display.maxLineLength) {
+ cm.display.maxLineLength = chWidth;
+ cm.display.maxLine = cur.line;
+ cm.display.maxLineChanged = true;
+ }
+ }
+ }
+ }
+
+ // Read and store the height of line widgets associated with the
+ // given line.
+ function updateWidgetHeight(line) {
+ if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) {
+ var w = line.widgets[i], parent = w.node.parentNode;
+ if (parent) { w.height = parent.offsetHeight; }
+ } }
+ }
+
+ // Compute the lines that are visible in a given viewport (defaults
+ // the the current scroll position). viewport may contain top,
+ // height, and ensure (see op.scrollToPos) properties.
+ function visibleLines(display, doc, viewport) {
+ var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;
+ top = Math.floor(top - paddingTop(display));
+ var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;
+
+ var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
+ // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
+ // forces those lines into the viewport (if possible).
+ if (viewport && viewport.ensure) {
+ var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;
+ if (ensureFrom < from) {
+ from = ensureFrom;
+ to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight);
+ } else if (Math.min(ensureTo, doc.lastLine()) >= to) {
+ from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight);
+ to = ensureTo;
+ }
+ }
+ return {from: from, to: Math.max(to, from + 1)}
+ }
+
+ // SCROLLING THINGS INTO VIEW
+
+ // If an editor sits on the top or bottom of the window, partially
+ // scrolled out of view, this ensures that the cursor is visible.
+ function maybeScrollWindow(cm, rect) {
+ if (signalDOMEvent(cm, "scrollCursorIntoView")) { return }
+
+ var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
+ if (rect.top + box.top < 0) { doScroll = true; }
+ else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false; }
+ if (doScroll != null && !phantom) {
+ var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;"));
+ cm.display.lineSpace.appendChild(scrollNode);
+ scrollNode.scrollIntoView(doScroll);
+ cm.display.lineSpace.removeChild(scrollNode);
+ }
+ }
+
+ // Scroll a given position into view (immediately), verifying that
+ // it actually became visible (as line heights are accurately
+ // measured, the position of something may 'drift' during drawing).
+ function scrollPosIntoView(cm, pos, end, margin) {
+ if (margin == null) { margin = 0; }
+ var rect;
+ if (!cm.options.lineWrapping && pos == end) {
+ // Set pos and end to the cursor positions around the character pos sticks to
+ // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch
+ // If pos == Pos(_, 0, "before"), pos and end are unchanged
+ pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos;
+ end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos;
+ }
+ for (var limit = 0; limit < 5; limit++) {
+ var changed = false;
+ var coords = cursorCoords(cm, pos);
+ var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
+ rect = {left: Math.min(coords.left, endCoords.left),
+ top: Math.min(coords.top, endCoords.top) - margin,
+ right: Math.max(coords.left, endCoords.left),
+ bottom: Math.max(coords.bottom, endCoords.bottom) + margin};
+ var scrollPos = calculateScrollPos(cm, rect);
+ var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
+ if (scrollPos.scrollTop != null) {
+ updateScrollTop(cm, scrollPos.scrollTop);
+ if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true; }
+ }
+ if (scrollPos.scrollLeft != null) {
+ setScrollLeft(cm, scrollPos.scrollLeft);
+ if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; }
+ }
+ if (!changed) { break }
+ }
+ return rect
+ }
+
+ // Scroll a given set of coordinates into view (immediately).
+ function scrollIntoView(cm, rect) {
+ var scrollPos = calculateScrollPos(cm, rect);
+ if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); }
+ if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); }
+ }
+
+ // Calculate a new scroll position needed to scroll the given
+ // rectangle into view. Returns an object with scrollTop and
+ // scrollLeft properties. When these are undefined, the
+ // vertical/horizontal position does not need to be adjusted.
+ function calculateScrollPos(cm, rect) {
+ var display = cm.display, snapMargin = textHeight(cm.display);
+ if (rect.top < 0) { rect.top = 0; }
+ var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
+ var screen = displayHeight(cm), result = {};
+ if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; }
+ var docBottom = cm.doc.height + paddingVert(display);
+ var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin;
+ if (rect.top < screentop) {
+ result.scrollTop = atTop ? 0 : rect.top;
+ } else if (rect.bottom > screentop + screen) {
+ var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen);
+ if (newTop != screentop) { result.scrollTop = newTop; }
+ }
+
+ var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;
+ var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0);
+ var tooWide = rect.right - rect.left > screenw;
+ if (tooWide) { rect.right = rect.left + screenw; }
+ if (rect.left < 10)
+ { result.scrollLeft = 0; }
+ else if (rect.left < screenleft)
+ { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)); }
+ else if (rect.right > screenw + screenleft - 3)
+ { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; }
+ return result
+ }
+
+ // Store a relative adjustment to the scroll position in the current
+ // operation (to be applied when the operation finishes).
+ function addToScrollTop(cm, top) {
+ if (top == null) { return }
+ resolveScrollToPos(cm);
+ cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top;
+ }
+
+ // Make sure that at the end of the operation the current cursor is
+ // shown.
+ function ensureCursorVisible(cm) {
+ resolveScrollToPos(cm);
+ var cur = cm.getCursor();
+ cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin};
+ }
+
+ function scrollToCoords(cm, x, y) {
+ if (x != null || y != null) { resolveScrollToPos(cm); }
+ if (x != null) { cm.curOp.scrollLeft = x; }
+ if (y != null) { cm.curOp.scrollTop = y; }
+ }
+
+ function scrollToRange(cm, range$$1) {
+ resolveScrollToPos(cm);
+ cm.curOp.scrollToPos = range$$1;
+ }
+
+ // When an operation has its scrollToPos property set, and another
+ // scroll action is applied before the end of the operation, this
+ // 'simulates' scrolling that position into view in a cheap way, so
+ // that the effect of intermediate scroll commands is not ignored.
+ function resolveScrollToPos(cm) {
+ var range$$1 = cm.curOp.scrollToPos;
+ if (range$$1) {
+ cm.curOp.scrollToPos = null;
+ var from = estimateCoords(cm, range$$1.from), to = estimateCoords(cm, range$$1.to);
+ scrollToCoordsRange(cm, from, to, range$$1.margin);
+ }
+ }
+
+ function scrollToCoordsRange(cm, from, to, margin) {
+ var sPos = calculateScrollPos(cm, {
+ left: Math.min(from.left, to.left),
+ top: Math.min(from.top, to.top) - margin,
+ right: Math.max(from.right, to.right),
+ bottom: Math.max(from.bottom, to.bottom) + margin
+ });
+ scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop);
+ }
+
+ // Sync the scrollable area and scrollbars, ensure the viewport
+ // covers the visible area.
+ function updateScrollTop(cm, val) {
+ if (Math.abs(cm.doc.scrollTop - val) < 2) { return }
+ if (!gecko) { updateDisplaySimple(cm, {top: val}); }
+ setScrollTop(cm, val, true);
+ if (gecko) { updateDisplaySimple(cm); }
+ startWorker(cm, 100);
+ }
+
+ function setScrollTop(cm, val, forceScroll) {
+ val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val);
+ if (cm.display.scroller.scrollTop == val && !forceScroll) { return }
+ cm.doc.scrollTop = val;
+ cm.display.scrollbars.setScrollTop(val);
+ if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; }
+ }
+
+ // Sync scroller and scrollbar, ensure the gutter elements are
+ // aligned.
+ function setScrollLeft(cm, val, isScroller, forceScroll) {
+ val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
+ if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return }
+ cm.doc.scrollLeft = val;
+ alignHorizontally(cm);
+ if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; }
+ cm.display.scrollbars.setScrollLeft(val);
+ }
+
+ // SCROLLBARS
+
+ // Prepare DOM reads needed to update the scrollbars. Done in one
+ // shot to minimize update/measure roundtrips.
+ function measureForScrollbars(cm) {
+ var d = cm.display, gutterW = d.gutters.offsetWidth;
+ var docH = Math.round(cm.doc.height + paddingVert(cm.display));
+ return {
+ clientHeight: d.scroller.clientHeight,
+ viewHeight: d.wrapper.clientHeight,
+ scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,
+ viewWidth: d.wrapper.clientWidth,
+ barLeft: cm.options.fixedGutter ? gutterW : 0,
+ docHeight: docH,
+ scrollHeight: docH + scrollGap(cm) + d.barHeight,
+ nativeBarWidth: d.nativeBarWidth,
+ gutterWidth: gutterW
+ }
+ }
+
+ var NativeScrollbars = function(place, scroll, cm) {
+ this.cm = cm;
+ var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
+ var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
+ vert.tabIndex = horiz.tabIndex = -1;
+ place(vert); place(horiz);
+
+ on(vert, "scroll", function () {
+ if (vert.clientHeight) { scroll(vert.scrollTop, "vertical"); }
+ });
+ on(horiz, "scroll", function () {
+ if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal"); }
+ });
+
+ this.checkedZeroWidth = false;
+ // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
+ if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; }
+ };
+
+ NativeScrollbars.prototype.update = function (measure) {
+ var needsH = measure.scrollWidth > measure.clientWidth + 1;
+ var needsV = measure.scrollHeight > measure.clientHeight + 1;
+ var sWidth = measure.nativeBarWidth;
+
+ if (needsV) {
+ this.vert.style.display = "block";
+ this.vert.style.bottom = needsH ? sWidth + "px" : "0";
+ var totalHeight = measure.viewHeight - (needsH ? sWidth : 0);
+ // A bug in IE8 can cause this value to be negative, so guard it.
+ this.vert.firstChild.style.height =
+ Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px";
+ } else {
+ this.vert.style.display = "";
+ this.vert.firstChild.style.height = "0";
+ }
+
+ if (needsH) {
+ this.horiz.style.display = "block";
+ this.horiz.style.right = needsV ? sWidth + "px" : "0";
+ this.horiz.style.left = measure.barLeft + "px";
+ var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0);
+ this.horiz.firstChild.style.width =
+ Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px";
+ } else {
+ this.horiz.style.display = "";
+ this.horiz.firstChild.style.width = "0";
+ }
+
+ if (!this.checkedZeroWidth && measure.clientHeight > 0) {
+ if (sWidth == 0) { this.zeroWidthHack(); }
+ this.checkedZeroWidth = true;
+ }
+
+ return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}
+ };
+
+ NativeScrollbars.prototype.setScrollLeft = function (pos) {
+ if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; }
+ if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz"); }
+ };
+
+ NativeScrollbars.prototype.setScrollTop = function (pos) {
+ if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; }
+ if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert"); }
+ };
+
+ NativeScrollbars.prototype.zeroWidthHack = function () {
+ var w = mac && !mac_geMountainLion ? "12px" : "18px";
+ this.horiz.style.height = this.vert.style.width = w;
+ this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none";
+ this.disableHoriz = new Delayed;
+ this.disableVert = new Delayed;
+ };
+
+ NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) {
+ bar.style.pointerEvents = "auto";
+ function maybeDisable() {
+ // To find out whether the scrollbar is still visible, we
+ // check whether the element under the pixel in the bottom
+ // right corner of the scrollbar box is the scrollbar box
+ // itself (when the bar is still visible) or its filler child
+ // (when the bar is hidden). If it is still visible, we keep
+ // it enabled, if it's hidden, we disable pointer events.
+ var box = bar.getBoundingClientRect();
+ var elt$$1 = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2)
+ : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1);
+ if (elt$$1 != bar) { bar.style.pointerEvents = "none"; }
+ else { delay.set(1000, maybeDisable); }
+ }
+ delay.set(1000, maybeDisable);
+ };
+
+ NativeScrollbars.prototype.clear = function () {
+ var parent = this.horiz.parentNode;
+ parent.removeChild(this.horiz);
+ parent.removeChild(this.vert);
+ };
+
+ var NullScrollbars = function () {};
+
+ NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} };
+ NullScrollbars.prototype.setScrollLeft = function () {};
+ NullScrollbars.prototype.setScrollTop = function () {};
+ NullScrollbars.prototype.clear = function () {};
+
+ function updateScrollbars(cm, measure) {
+ if (!measure) { measure = measureForScrollbars(cm); }
+ var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight;
+ updateScrollbarsInner(cm, measure);
+ for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {
+ if (startWidth != cm.display.barWidth && cm.options.lineWrapping)
+ { updateHeightsInViewport(cm); }
+ updateScrollbarsInner(cm, measureForScrollbars(cm));
+ startWidth = cm.display.barWidth; startHeight = cm.display.barHeight;
+ }
+ }
+
+ // Re-synchronize the fake scrollbars with the actual size of the
+ // content.
+ function updateScrollbarsInner(cm, measure) {
+ var d = cm.display;
+ var sizes = d.scrollbars.update(measure);
+
+ d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px";
+ d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px";
+ d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent";
+
+ if (sizes.right && sizes.bottom) {
+ d.scrollbarFiller.style.display = "block";
+ d.scrollbarFiller.style.height = sizes.bottom + "px";
+ d.scrollbarFiller.style.width = sizes.right + "px";
+ } else { d.scrollbarFiller.style.display = ""; }
+ if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
+ d.gutterFiller.style.display = "block";
+ d.gutterFiller.style.height = sizes.bottom + "px";
+ d.gutterFiller.style.width = measure.gutterWidth + "px";
+ } else { d.gutterFiller.style.display = ""; }
+ }
+
+ var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars};
+
+ function initScrollbars(cm) {
+ if (cm.display.scrollbars) {
+ cm.display.scrollbars.clear();
+ if (cm.display.scrollbars.addClass)
+ { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); }
+ }
+
+ cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) {
+ cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller);
+ // Prevent clicks in the scrollbars from killing focus
+ on(node, "mousedown", function () {
+ if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); }
+ });
+ node.setAttribute("cm-not-content", "true");
+ }, function (pos, axis) {
+ if (axis == "horizontal") { setScrollLeft(cm, pos); }
+ else { updateScrollTop(cm, pos); }
+ }, cm);
+ if (cm.display.scrollbars.addClass)
+ { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); }
+ }
+
+ // Operations are used to wrap a series of changes to the editor
+ // state in such a way that each change won't have to update the
+ // cursor and display (which would be awkward, slow, and
+ // error-prone). Instead, display updates are batched and then all
+ // combined and executed at once.
+
+ var nextOpId = 0;
+ // Start a new operation.
+ function startOperation(cm) {
+ cm.curOp = {
+ cm: cm,
+ viewChanged: false, // Flag that indicates that lines might need to be redrawn
+ startHeight: cm.doc.height, // Used to detect need to update scrollbar
+ forceUpdate: false, // Used to force a redraw
+ updateInput: 0, // Whether to reset the input textarea
+ typing: false, // Whether this reset should be careful to leave existing text (for compositing)
+ changeObjs: null, // Accumulated changes, for firing change events
+ cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
+ cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
+ selectionChanged: false, // Whether the selection needs to be redrawn
+ updateMaxLine: false, // Set when the widest line needs to be determined anew
+ scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
+ scrollToPos: null, // Used to scroll to a specific position
+ focus: false,
+ id: ++nextOpId // Unique ID
+ };
+ pushOperation(cm.curOp);
+ }
+
+ // Finish an operation, updating the display and signalling delayed events
+ function endOperation(cm) {
+ var op = cm.curOp;
+ if (op) { finishOperation(op, function (group) {
+ for (var i = 0; i < group.ops.length; i++)
+ { group.ops[i].cm.curOp = null; }
+ endOperations(group);
+ }); }
+ }
+
+ // The DOM updates done when an operation finishes are batched so
+ // that the minimum number of relayouts are required.
+ function endOperations(group) {
+ var ops = group.ops;
+ for (var i = 0; i < ops.length; i++) // Read DOM
+ { endOperation_R1(ops[i]); }
+ for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe)
+ { endOperation_W1(ops[i$1]); }
+ for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM
+ { endOperation_R2(ops[i$2]); }
+ for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe)
+ { endOperation_W2(ops[i$3]); }
+ for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM
+ { endOperation_finish(ops[i$4]); }
+ }
+
+ function endOperation_R1(op) {
+ var cm = op.cm, display = cm.display;
+ maybeClipScrollbars(cm);
+ if (op.updateMaxLine) { findMaxLine(cm); }
+
+ op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
+ op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
+ op.scrollToPos.to.line >= display.viewTo) ||
+ display.maxLineChanged && cm.options.lineWrapping;
+ op.update = op.mustUpdate &&
+ new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
+ }
+
+ function endOperation_W1(op) {
+ op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);
+ }
+
+ function endOperation_R2(op) {
+ var cm = op.cm, display = cm.display;
+ if (op.updatedDisplay) { updateHeightsInViewport(cm); }
+
+ op.barMeasure = measureForScrollbars(cm);
+
+ // If the max line changed since it was last measured, measure it,
+ // and ensure the document's width matches it.
+ // updateDisplay_W2 will use these properties to do the actual resizing
+ if (display.maxLineChanged && !cm.options.lineWrapping) {
+ op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;
+ cm.display.sizerWidth = op.adjustWidthTo;
+ op.barMeasure.scrollWidth =
+ Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth);
+ op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm));
+ }
+
+ if (op.updatedDisplay || op.selectionChanged)
+ { op.preparedSelection = display.input.prepareSelection(); }
+ }
+
+ function endOperation_W2(op) {
+ var cm = op.cm;
+
+ if (op.adjustWidthTo != null) {
+ cm.display.sizer.style.minWidth = op.adjustWidthTo + "px";
+ if (op.maxScrollLeft < cm.doc.scrollLeft)
+ { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); }
+ cm.display.maxLineChanged = false;
+ }
+
+ var takeFocus = op.focus && op.focus == activeElt();
+ if (op.preparedSelection)
+ { cm.display.input.showSelection(op.preparedSelection, takeFocus); }
+ if (op.updatedDisplay || op.startHeight != cm.doc.height)
+ { updateScrollbars(cm, op.barMeasure); }
+ if (op.updatedDisplay)
+ { setDocumentHeight(cm, op.barMeasure); }
+
+ if (op.selectionChanged) { restartBlink(cm); }
+
+ if (cm.state.focused && op.updateInput)
+ { cm.display.input.reset(op.typing); }
+ if (takeFocus) { ensureFocus(op.cm); }
+ }
+
+ function endOperation_finish(op) {
+ var cm = op.cm, display = cm.display, doc = cm.doc;
+
+ if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); }
+
+ // Abort mouse wheel delta measurement, when scrolling explicitly
+ if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
+ { display.wheelStartX = display.wheelStartY = null; }
+
+ // Propagate the scroll position to the actual DOM scroller
+ if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); }
+
+ if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); }
+ // If we need to scroll a specific position into view, do so.
+ if (op.scrollToPos) {
+ var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
+ clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);
+ maybeScrollWindow(cm, rect);
+ }
+
+ // Fire events for markers that are hidden/unidden by editing or
+ // undoing
+ var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
+ if (hidden) { for (var i = 0; i < hidden.length; ++i)
+ { if (!hidden[i].lines.length) { signal(hidden[i], "hide"); } } }
+ if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1)
+ { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide"); } } }
+
+ if (display.wrapper.offsetHeight)
+ { doc.scrollTop = cm.display.scroller.scrollTop; }
+
+ // Fire change events, and delayed event handlers
+ if (op.changeObjs)
+ { signal(cm, "changes", cm, op.changeObjs); }
+ if (op.update)
+ { op.update.finish(); }
+ }
+
+ // Run the given function in an operation
+ function runInOp(cm, f) {
+ if (cm.curOp) { return f() }
+ startOperation(cm);
+ try { return f() }
+ finally { endOperation(cm); }
+ }
+ // Wraps a function in an operation. Returns the wrapped function.
+ function operation(cm, f) {
+ return function() {
+ if (cm.curOp) { return f.apply(cm, arguments) }
+ startOperation(cm);
+ try { return f.apply(cm, arguments) }
+ finally { endOperation(cm); }
+ }
+ }
+ // Used to add methods to editor and doc instances, wrapping them in
+ // operations.
+ function methodOp(f) {
+ return function() {
+ if (this.curOp) { return f.apply(this, arguments) }
+ startOperation(this);
+ try { return f.apply(this, arguments) }
+ finally { endOperation(this); }
+ }
+ }
+ function docMethodOp(f) {
+ return function() {
+ var cm = this.cm;
+ if (!cm || cm.curOp) { return f.apply(this, arguments) }
+ startOperation(cm);
+ try { return f.apply(this, arguments) }
+ finally { endOperation(cm); }
+ }
+ }
+
+ // HIGHLIGHT WORKER
+
+ function startWorker(cm, time) {
+ if (cm.doc.highlightFrontier < cm.display.viewTo)
+ { cm.state.highlight.set(time, bind(highlightWorker, cm)); }
+ }
+
+ function highlightWorker(cm) {
+ var doc = cm.doc;
+ if (doc.highlightFrontier >= cm.display.viewTo) { return }
+ var end = +new Date + cm.options.workTime;
+ var context = getContextBefore(cm, doc.highlightFrontier);
+ var changedLines = [];
+
+ doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) {
+ if (context.line >= cm.display.viewFrom) { // Visible
+ var oldStyles = line.styles;
+ var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null;
+ var highlighted = highlightLine(cm, line, context, true);
+ if (resetState) { context.state = resetState; }
+ line.styles = highlighted.styles;
+ var oldCls = line.styleClasses, newCls = highlighted.classes;
+ if (newCls) { line.styleClasses = newCls; }
+ else if (oldCls) { line.styleClasses = null; }
+ var ischange = !oldStyles || oldStyles.length != line.styles.length ||
+ oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);
+ for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; }
+ if (ischange) { changedLines.push(context.line); }
+ line.stateAfter = context.save();
+ context.nextLine();
+ } else {
+ if (line.text.length <= cm.options.maxHighlightLength)
+ { processLine(cm, line.text, context); }
+ line.stateAfter = context.line % 5 == 0 ? context.save() : null;
+ context.nextLine();
+ }
+ if (+new Date > end) {
+ startWorker(cm, cm.options.workDelay);
+ return true
+ }
+ });
+ doc.highlightFrontier = context.line;
+ doc.modeFrontier = Math.max(doc.modeFrontier, context.line);
+ if (changedLines.length) { runInOp(cm, function () {
+ for (var i = 0; i < changedLines.length; i++)
+ { regLineChange(cm, changedLines[i], "text"); }
+ }); }
+ }
+
+ // DISPLAY DRAWING
+
+ var DisplayUpdate = function(cm, viewport, force) {
+ var display = cm.display;
+
+ this.viewport = viewport;
+ // Store some values that we'll need later (but don't want to force a relayout for)
+ this.visible = visibleLines(display, cm.doc, viewport);
+ this.editorIsHidden = !display.wrapper.offsetWidth;
+ this.wrapperHeight = display.wrapper.clientHeight;
+ this.wrapperWidth = display.wrapper.clientWidth;
+ this.oldDisplayWidth = displayWidth(cm);
+ this.force = force;
+ this.dims = getDimensions(cm);
+ this.events = [];
+ };
+
+ DisplayUpdate.prototype.signal = function (emitter, type) {
+ if (hasHandler(emitter, type))
+ { this.events.push(arguments); }
+ };
+ DisplayUpdate.prototype.finish = function () {
+ var this$1 = this;
+
+ for (var i = 0; i < this.events.length; i++)
+ { signal.apply(null, this$1.events[i]); }
+ };
+
+ function maybeClipScrollbars(cm) {
+ var display = cm.display;
+ if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
+ display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth;
+ display.heightForcer.style.height = scrollGap(cm) + "px";
+ display.sizer.style.marginBottom = -display.nativeBarWidth + "px";
+ display.sizer.style.borderRightWidth = scrollGap(cm) + "px";
+ display.scrollbarsClipped = true;
+ }
+ }
+
+ function selectionSnapshot(cm) {
+ if (cm.hasFocus()) { return null }
+ var active = activeElt();
+ if (!active || !contains(cm.display.lineDiv, active)) { return null }
+ var result = {activeElt: active};
+ if (window.getSelection) {
+ var sel = window.getSelection();
+ if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {
+ result.anchorNode = sel.anchorNode;
+ result.anchorOffset = sel.anchorOffset;
+ result.focusNode = sel.focusNode;
+ result.focusOffset = sel.focusOffset;
+ }
+ }
+ return result
+ }
+
+ function restoreSelection(snapshot) {
+ if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return }
+ snapshot.activeElt.focus();
+ if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {
+ var sel = window.getSelection(), range$$1 = document.createRange();
+ range$$1.setEnd(snapshot.anchorNode, snapshot.anchorOffset);
+ range$$1.collapse(false);
+ sel.removeAllRanges();
+ sel.addRange(range$$1);
+ sel.extend(snapshot.focusNode, snapshot.focusOffset);
+ }
+ }
+
+ // Does the actual updating of the line display. Bails out
+ // (returning false) when there is nothing to be done and forced is
+ // false.
+ function updateDisplayIfNeeded(cm, update) {
+ var display = cm.display, doc = cm.doc;
+
+ if (update.editorIsHidden) {
+ resetView(cm);
+ return false
+ }
+
+ // Bail out if the visible area is already rendered and nothing changed.
+ if (!update.force &&
+ update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
+ (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
+ display.renderedView == display.view && countDirtyView(cm) == 0)
+ { return false }
+
+ if (maybeUpdateLineNumberWidth(cm)) {
+ resetView(cm);
+ update.dims = getDimensions(cm);
+ }
+
+ // Compute a suitable new viewport (from & to)
+ var end = doc.first + doc.size;
+ var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
+ var to = Math.min(end, update.visible.to + cm.options.viewportMargin);
+ if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); }
+ if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); }
+ if (sawCollapsedSpans) {
+ from = visualLineNo(cm.doc, from);
+ to = visualLineEndNo(cm.doc, to);
+ }
+
+ var different = from != display.viewFrom || to != display.viewTo ||
+ display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;
+ adjustView(cm, from, to);
+
+ display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
+ // Position the mover div to align with the current scroll position
+ cm.display.mover.style.top = display.viewOffset + "px";
+
+ var toUpdate = countDirtyView(cm);
+ if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
+ (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
+ { return false }
+
+ // For big changes, we hide the enclosing element during the
+ // update, since that speeds up the operations on most browsers.
+ var selSnapshot = selectionSnapshot(cm);
+ if (toUpdate > 4) { display.lineDiv.style.display = "none"; }
+ patchDisplay(cm, display.updateLineNumbers, update.dims);
+ if (toUpdate > 4) { display.lineDiv.style.display = ""; }
+ display.renderedView = display.view;
+ // There might have been a widget with a focused element that got
+ // hidden or updated, if so re-focus it.
+ restoreSelection(selSnapshot);
+
+ // Prevent selection and cursors from interfering with the scroll
+ // width and height.
+ removeChildren(display.cursorDiv);
+ removeChildren(display.selectionDiv);
+ display.gutters.style.height = display.sizer.style.minHeight = 0;
+
+ if (different) {
+ display.lastWrapHeight = update.wrapperHeight;
+ display.lastWrapWidth = update.wrapperWidth;
+ startWorker(cm, 400);
+ }
+
+ display.updateLineNumbers = null;
+
+ return true
+ }
+
+ function postUpdateDisplay(cm, update) {
+ var viewport = update.viewport;
+
+ for (var first = true;; first = false) {
+ if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {
+ // Clip forced viewport to actual scrollable area.
+ if (viewport && viewport.top != null)
+ { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; }
+ // Updated line heights might result in the drawn area not
+ // actually covering the viewport. Keep looping until it does.
+ update.visible = visibleLines(cm.display, cm.doc, viewport);
+ if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
+ { break }
+ }
+ if (!updateDisplayIfNeeded(cm, update)) { break }
+ updateHeightsInViewport(cm);
+ var barMeasure = measureForScrollbars(cm);
+ updateSelection(cm);
+ updateScrollbars(cm, barMeasure);
+ setDocumentHeight(cm, barMeasure);
+ update.force = false;
+ }
+
+ update.signal(cm, "update", cm);
+ if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
+ update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
+ cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo;
+ }
+ }
+
+ function updateDisplaySimple(cm, viewport) {
+ var update = new DisplayUpdate(cm, viewport);
+ if (updateDisplayIfNeeded(cm, update)) {
+ updateHeightsInViewport(cm);
+ postUpdateDisplay(cm, update);
+ var barMeasure = measureForScrollbars(cm);
+ updateSelection(cm);
+ updateScrollbars(cm, barMeasure);
+ setDocumentHeight(cm, barMeasure);
+ update.finish();
+ }
+ }
+
+ // Sync the actual display DOM structure with display.view, removing
+ // nodes for lines that are no longer in view, and creating the ones
+ // that are not there yet, and updating the ones that are out of
+ // date.
+ function patchDisplay(cm, updateNumbersFrom, dims) {
+ var display = cm.display, lineNumbers = cm.options.lineNumbers;
+ var container = display.lineDiv, cur = container.firstChild;
+
+ function rm(node) {
+ var next = node.nextSibling;
+ // Works around a throw-scroll bug in OS X Webkit
+ if (webkit && mac && cm.display.currentWheelTarget == node)
+ { node.style.display = "none"; }
+ else
+ { node.parentNode.removeChild(node); }
+ return next
+ }
+
+ var view = display.view, lineN = display.viewFrom;
+ // Loop over the elements in the view, syncing cur (the DOM nodes
+ // in display.lineDiv) with the view as we go.
+ for (var i = 0; i < view.length; i++) {
+ var lineView = view[i];
+ if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
+ var node = buildLineElement(cm, lineView, lineN, dims);
+ container.insertBefore(node, cur);
+ } else { // Already drawn
+ while (cur != lineView.node) { cur = rm(cur); }
+ var updateNumber = lineNumbers && updateNumbersFrom != null &&
+ updateNumbersFrom <= lineN && lineView.lineNumber;
+ if (lineView.changes) {
+ if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false; }
+ updateLineForChanges(cm, lineView, lineN, dims);
+ }
+ if (updateNumber) {
+ removeChildren(lineView.lineNumber);
+ lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));
+ }
+ cur = lineView.node.nextSibling;
+ }
+ lineN += lineView.size;
+ }
+ while (cur) { cur = rm(cur); }
+ }
+
+ function updateGutterSpace(display) {
+ var width = display.gutters.offsetWidth;
+ display.sizer.style.marginLeft = width + "px";
+ }
+
+ function setDocumentHeight(cm, measure) {
+ cm.display.sizer.style.minHeight = measure.docHeight + "px";
+ cm.display.heightForcer.style.top = measure.docHeight + "px";
+ cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px";
+ }
+
+ // Re-align line numbers and gutter marks to compensate for
+ // horizontal scrolling.
+ function alignHorizontally(cm) {
+ var display = cm.display, view = display.view;
+ if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return }
+ var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
+ var gutterW = display.gutters.offsetWidth, left = comp + "px";
+ for (var i = 0; i < view.length; i++) { if (!view[i].hidden) {
+ if (cm.options.fixedGutter) {
+ if (view[i].gutter)
+ { view[i].gutter.style.left = left; }
+ if (view[i].gutterBackground)
+ { view[i].gutterBackground.style.left = left; }
+ }
+ var align = view[i].alignable;
+ if (align) { for (var j = 0; j < align.length; j++)
+ { align[j].style.left = left; } }
+ } }
+ if (cm.options.fixedGutter)
+ { display.gutters.style.left = (comp + gutterW) + "px"; }
+ }
+
+ // Used to ensure that the line number gutter is still the right
+ // size for the current document size. Returns true when an update
+ // is needed.
+ function maybeUpdateLineNumberWidth(cm) {
+ if (!cm.options.lineNumbers) { return false }
+ var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
+ if (last.length != display.lineNumChars) {
+ var test = display.measure.appendChild(elt("div", [elt("div", last)],
+ "CodeMirror-linenumber CodeMirror-gutter-elt"));
+ var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
+ display.lineGutter.style.width = "";
+ display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1;
+ display.lineNumWidth = display.lineNumInnerWidth + padding;
+ display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
+ display.lineGutter.style.width = display.lineNumWidth + "px";
+ updateGutterSpace(cm.display);
+ return true
+ }
+ return false
+ }
+
+ function getGutters(gutters, lineNumbers) {
+ var result = [], sawLineNumbers = false;
+ for (var i = 0; i < gutters.length; i++) {
+ var name = gutters[i], style = null;
+ if (typeof name != "string") { style = name.style; name = name.className; }
+ if (name == "CodeMirror-linenumbers") {
+ if (!lineNumbers) { continue }
+ else { sawLineNumbers = true; }
+ }
+ result.push({className: name, style: style});
+ }
+ if (lineNumbers && !sawLineNumbers) { result.push({className: "CodeMirror-linenumbers", style: null}); }
+ return result
+ }
+
+ // Rebuild the gutter elements, ensure the margin to the left of the
+ // code matches their width.
+ function renderGutters(display) {
+ var gutters = display.gutters, specs = display.gutterSpecs;
+ removeChildren(gutters);
+ display.lineGutter = null;
+ for (var i = 0; i < specs.length; ++i) {
+ var ref = specs[i];
+ var className = ref.className;
+ var style = ref.style;
+ var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className));
+ if (style) { gElt.style.cssText = style; }
+ if (className == "CodeMirror-linenumbers") {
+ display.lineGutter = gElt;
+ gElt.style.width = (display.lineNumWidth || 1) + "px";
+ }
+ }
+ gutters.style.display = specs.length ? "" : "none";
+ updateGutterSpace(display);
+ }
+
+ function updateGutters(cm) {
+ renderGutters(cm.display);
+ regChange(cm);
+ alignHorizontally(cm);
+ }
+
+ // The display handles the DOM integration, both for input reading
+ // and content drawing. It holds references to DOM nodes and
+ // display-related state.
+
+ function Display(place, doc, input, options) {
+ var d = this;
+ this.input = input;
+
+ // Covers bottom-right square when both scrollbars are present.
+ d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
+ d.scrollbarFiller.setAttribute("cm-not-content", "true");
+ // Covers bottom of gutter when coverGutterNextToScrollbar is on
+ // and h scrollbar is present.
+ d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
+ d.gutterFiller.setAttribute("cm-not-content", "true");
+ // Will contain the actual code, positioned to cover the viewport.
+ d.lineDiv = eltP("div", null, "CodeMirror-code");
+ // Elements are added to these to represent selection and cursors.
+ d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
+ d.cursorDiv = elt("div", null, "CodeMirror-cursors");
+ // A visibility: hidden element used to find the size of things.
+ d.measure = elt("div", null, "CodeMirror-measure");
+ // When lines outside of the viewport are measured, they are drawn in this.
+ d.lineMeasure = elt("div", null, "CodeMirror-measure");
+ // Wraps everything that needs to exist inside the vertically-padded coordinate system
+ d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
+ null, "position: relative; outline: none");
+ var lines = eltP("div", [d.lineSpace], "CodeMirror-lines");
+ // Moved around its parent to cover visible view.
+ d.mover = elt("div", [lines], null, "position: relative");
+ // Set to the height of the document, allowing scrolling.
+ d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
+ d.sizerWidth = null;
+ // Behavior of elts with overflow: auto and padding is
+ // inconsistent across browsers. This is used to ensure the
+ // scrollable area is big enough.
+ d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;");
+ // Will contain the gutters, if any.
+ d.gutters = elt("div", null, "CodeMirror-gutters");
+ d.lineGutter = null;
+ // Actual scrollable element.
+ d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
+ d.scroller.setAttribute("tabIndex", "-1");
+ // The element in which the editor lives.
+ d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
+
+ // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
+ if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
+ if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; }
+
+ if (place) {
+ if (place.appendChild) { place.appendChild(d.wrapper); }
+ else { place(d.wrapper); }
+ }
+
+ // Current rendered range (may be bigger than the view window).
+ d.viewFrom = d.viewTo = doc.first;
+ d.reportedViewFrom = d.reportedViewTo = doc.first;
+ // Information about the rendered lines.
+ d.view = [];
+ d.renderedView = null;
+ // Holds info about a single rendered line when it was rendered
+ // for measurement, while not in view.
+ d.externalMeasured = null;
+ // Empty space (in pixels) above the view
+ d.viewOffset = 0;
+ d.lastWrapHeight = d.lastWrapWidth = 0;
+ d.updateLineNumbers = null;
+
+ d.nativeBarWidth = d.barHeight = d.barWidth = 0;
+ d.scrollbarsClipped = false;
+
+ // Used to only resize the line number gutter when necessary (when
+ // the amount of lines crosses a boundary that makes its width change)
+ d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
+ // Set to true when a non-horizontal-scrolling line widget is
+ // added. As an optimization, line widget aligning is skipped when
+ // this is false.
+ d.alignWidgets = false;
+
+ d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
+
+ // Tracks the maximum line length so that the horizontal scrollbar
+ // can be kept static when scrolling.
+ d.maxLine = null;
+ d.maxLineLength = 0;
+ d.maxLineChanged = false;
+
+ // Used for measuring wheel scrolling granularity
+ d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
+
+ // True when shift is held down.
+ d.shift = false;
+
+ // Used to track whether anything happened since the context menu
+ // was opened.
+ d.selForContextMenu = null;
+
+ d.activeTouch = null;
+
+ d.gutterSpecs = getGutters(options.gutters, options.lineNumbers);
+ renderGutters(d);
+
+ input.init(d);
+ }
+
+ // Since the delta values reported on mouse wheel events are
+ // unstandardized between browsers and even browser versions, and
+ // generally horribly unpredictable, this code starts by measuring
+ // the scroll effect that the first few mouse wheel events have,
+ // and, from that, detects the way it can convert deltas to pixel
+ // offsets afterwards.
+ //
+ // The reason we want to know the amount a wheel event will scroll
+ // is that it gives us a chance to update the display before the
+ // actual scrolling happens, reducing flickering.
+
+ var wheelSamples = 0, wheelPixelsPerUnit = null;
+ // Fill in a browser-detected starting value on browsers where we
+ // know one. These don't have to be accurate -- the result of them
+ // being wrong would just be a slight flicker on the first wheel
+ // scroll (if it is large enough).
+ if (ie) { wheelPixelsPerUnit = -.53; }
+ else if (gecko) { wheelPixelsPerUnit = 15; }
+ else if (chrome) { wheelPixelsPerUnit = -.7; }
+ else if (safari) { wheelPixelsPerUnit = -1/3; }
+
+ function wheelEventDelta(e) {
+ var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
+ if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail; }
+ if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail; }
+ else if (dy == null) { dy = e.wheelDelta; }
+ return {x: dx, y: dy}
+ }
+ function wheelEventPixels(e) {
+ var delta = wheelEventDelta(e);
+ delta.x *= wheelPixelsPerUnit;
+ delta.y *= wheelPixelsPerUnit;
+ return delta
+ }
+
+ function onScrollWheel(cm, e) {
+ var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;
+
+ var display = cm.display, scroll = display.scroller;
+ // Quit if there's nothing to scroll here
+ var canScrollX = scroll.scrollWidth > scroll.clientWidth;
+ var canScrollY = scroll.scrollHeight > scroll.clientHeight;
+ if (!(dx && canScrollX || dy && canScrollY)) { return }
+
+ // Webkit browsers on OS X abort momentum scrolls when the target
+ // of the scroll event is removed from the scrollable element.
+ // This hack (see related code in patchDisplay) makes sure the
+ // element is kept around.
+ if (dy && mac && webkit) {
+ outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
+ for (var i = 0; i < view.length; i++) {
+ if (view[i].node == cur) {
+ cm.display.currentWheelTarget = cur;
+ break outer
+ }
+ }
+ }
+ }
+
+ // On some browsers, horizontal scrolling will cause redraws to
+ // happen before the gutter has been realigned, causing it to
+ // wriggle around in a most unseemly way. When we have an
+ // estimated pixels/delta value, we just handle horizontal
+ // scrolling entirely here. It'll be slightly off from native, but
+ // better than glitching out.
+ if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
+ if (dy && canScrollY)
+ { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); }
+ setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit));
+ // Only prevent default scrolling if vertical scrolling is
+ // actually possible. Otherwise, it causes vertical scroll
+ // jitter on OSX trackpads when deltaX is small and deltaY
+ // is large (issue #3579)
+ if (!dy || (dy && canScrollY))
+ { e_preventDefault(e); }
+ display.wheelStartX = null; // Abort measurement, if in progress
+ return
+ }
+
+ // 'Project' the visible viewport to cover the area that is being
+ // scrolled into view (if we know enough to estimate it).
+ if (dy && wheelPixelsPerUnit != null) {
+ var pixels = dy * wheelPixelsPerUnit;
+ var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
+ if (pixels < 0) { top = Math.max(0, top + pixels - 50); }
+ else { bot = Math.min(cm.doc.height, bot + pixels + 50); }
+ updateDisplaySimple(cm, {top: top, bottom: bot});
+ }
+
+ if (wheelSamples < 20) {
+ if (display.wheelStartX == null) {
+ display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
+ display.wheelDX = dx; display.wheelDY = dy;
+ setTimeout(function () {
+ if (display.wheelStartX == null) { return }
+ var movedX = scroll.scrollLeft - display.wheelStartX;
+ var movedY = scroll.scrollTop - display.wheelStartY;
+ var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
+ (movedX && display.wheelDX && movedX / display.wheelDX);
+ display.wheelStartX = display.wheelStartY = null;
+ if (!sample) { return }
+ wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
+ ++wheelSamples;
+ }, 200);
+ } else {
+ display.wheelDX += dx; display.wheelDY += dy;
+ }
+ }
+ }
+
+ // Selection objects are immutable. A new one is created every time
+ // the selection changes. A selection is one or more non-overlapping
+ // (and non-touching) ranges, sorted, and an integer that indicates
+ // which one is the primary selection (the one that's scrolled into
+ // view, that getCursor returns, etc).
+ var Selection = function(ranges, primIndex) {
+ this.ranges = ranges;
+ this.primIndex = primIndex;
+ };
+
+ Selection.prototype.primary = function () { return this.ranges[this.primIndex] };
+
+ Selection.prototype.equals = function (other) {
+ var this$1 = this;
+
+ if (other == this) { return true }
+ if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false }
+ for (var i = 0; i < this.ranges.length; i++) {
+ var here = this$1.ranges[i], there = other.ranges[i];
+ if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false }
+ }
+ return true
+ };
+
+ Selection.prototype.deepCopy = function () {
+ var this$1 = this;
+
+ var out = [];
+ for (var i = 0; i < this.ranges.length; i++)
+ { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)); }
+ return new Selection(out, this.primIndex)
+ };
+
+ Selection.prototype.somethingSelected = function () {
+ var this$1 = this;
+
+ for (var i = 0; i < this.ranges.length; i++)
+ { if (!this$1.ranges[i].empty()) { return true } }
+ return false
+ };
+
+ Selection.prototype.contains = function (pos, end) {
+ var this$1 = this;
+
+ if (!end) { end = pos; }
+ for (var i = 0; i < this.ranges.length; i++) {
+ var range = this$1.ranges[i];
+ if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
+ { return i }
+ }
+ return -1
+ };
+
+ var Range = function(anchor, head) {
+ this.anchor = anchor; this.head = head;
+ };
+
+ Range.prototype.from = function () { return minPos(this.anchor, this.head) };
+ Range.prototype.to = function () { return maxPos(this.anchor, this.head) };
+ Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch };
+
+ // Take an unsorted, potentially overlapping set of ranges, and
+ // build a selection out of it. 'Consumes' ranges array (modifying
+ // it).
+ function normalizeSelection(cm, ranges, primIndex) {
+ var mayTouch = cm && cm.options.selectionsMayTouch;
+ var prim = ranges[primIndex];
+ ranges.sort(function (a, b) { return cmp(a.from(), b.from()); });
+ primIndex = indexOf(ranges, prim);
+ for (var i = 1; i < ranges.length; i++) {
+ var cur = ranges[i], prev = ranges[i - 1];
+ var diff = cmp(prev.to(), cur.from());
+ if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) {
+ var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());
+ var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;
+ if (i <= primIndex) { --primIndex; }
+ ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));
+ }
+ }
+ return new Selection(ranges, primIndex)
+ }
+
+ function simpleSelection(anchor, head) {
+ return new Selection([new Range(anchor, head || anchor)], 0)
+ }
+
+ // Compute the position of the end of a change (its 'to' property
+ // refers to the pre-change end).
+ function changeEnd(change) {
+ if (!change.text) { return change.to }
+ return Pos(change.from.line + change.text.length - 1,
+ lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0))
+ }
+
+ // Adjust a position to refer to the post-change position of the
+ // same text, or the end of the change if the change covers it.
+ function adjustForChange(pos, change) {
+ if (cmp(pos, change.from) < 0) { return pos }
+ if (cmp(pos, change.to) <= 0) { return changeEnd(change) }
+
+ var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
+ if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; }
+ return Pos(line, ch)
+ }
+
+ function computeSelAfterChange(doc, change) {
+ var out = [];
+ for (var i = 0; i < doc.sel.ranges.length; i++) {
+ var range = doc.sel.ranges[i];
+ out.push(new Range(adjustForChange(range.anchor, change),
+ adjustForChange(range.head, change)));
+ }
+ return normalizeSelection(doc.cm, out, doc.sel.primIndex)
+ }
+
+ function offsetPos(pos, old, nw) {
+ if (pos.line == old.line)
+ { return Pos(nw.line, pos.ch - old.ch + nw.ch) }
+ else
+ { return Pos(nw.line + (pos.line - old.line), pos.ch) }
+ }
+
+ // Used by replaceSelections to allow moving the selection to the
+ // start or around the replaced test. Hint may be "start" or "around".
+ function computeReplacedSel(doc, changes, hint) {
+ var out = [];
+ var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;
+ for (var i = 0; i < changes.length; i++) {
+ var change = changes[i];
+ var from = offsetPos(change.from, oldPrev, newPrev);
+ var to = offsetPos(changeEnd(change), oldPrev, newPrev);
+ oldPrev = change.to;
+ newPrev = to;
+ if (hint == "around") {
+ var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;
+ out[i] = new Range(inv ? to : from, inv ? from : to);
+ } else {
+ out[i] = new Range(from, from);
+ }
+ }
+ return new Selection(out, doc.sel.primIndex)
+ }
+
+ // Used to get the editor into a consistent state again when options change.
+
+ function loadMode(cm) {
+ cm.doc.mode = getMode(cm.options, cm.doc.modeOption);
+ resetModeState(cm);
+ }
+
+ function resetModeState(cm) {
+ cm.doc.iter(function (line) {
+ if (line.stateAfter) { line.stateAfter = null; }
+ if (line.styles) { line.styles = null; }
+ });
+ cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first;
+ startWorker(cm, 100);
+ cm.state.modeGen++;
+ if (cm.curOp) { regChange(cm); }
+ }
+
+ // DOCUMENT DATA STRUCTURE
+
+ // By default, updates that start and end at the beginning of a line
+ // are treated specially, in order to make the association of line
+ // widgets and marker elements with the text behave more intuitive.
+ function isWholeLineUpdate(doc, change) {
+ return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
+ (!doc.cm || doc.cm.options.wholeLineUpdateBefore)
+ }
+
+ // Perform a change on the document data structure.
+ function updateDoc(doc, change, markedSpans, estimateHeight$$1) {
+ function spansFor(n) {return markedSpans ? markedSpans[n] : null}
+ function update(line, text, spans) {
+ updateLine(line, text, spans, estimateHeight$$1);
+ signalLater(line, "change", line, change);
+ }
+ function linesFor(start, end) {
+ var result = [];
+ for (var i = start; i < end; ++i)
+ { result.push(new Line(text[i], spansFor(i), estimateHeight$$1)); }
+ return result
+ }
+
+ var from = change.from, to = change.to, text = change.text;
+ var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
+ var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
+
+ // Adjust the line structure
+ if (change.full) {
+ doc.insert(0, linesFor(0, text.length));
+ doc.remove(text.length, doc.size - text.length);
+ } else if (isWholeLineUpdate(doc, change)) {
+ // This is a whole-line replace. Treated specially to make
+ // sure line objects move the way they are supposed to.
+ var added = linesFor(0, text.length - 1);
+ update(lastLine, lastLine.text, lastSpans);
+ if (nlines) { doc.remove(from.line, nlines); }
+ if (added.length) { doc.insert(from.line, added); }
+ } else if (firstLine == lastLine) {
+ if (text.length == 1) {
+ update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
+ } else {
+ var added$1 = linesFor(1, text.length - 1);
+ added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight$$1));
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
+ doc.insert(from.line + 1, added$1);
+ }
+ } else if (text.length == 1) {
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));
+ doc.remove(from.line + 1, nlines);
+ } else {
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
+ update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
+ var added$2 = linesFor(1, text.length - 1);
+ if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); }
+ doc.insert(from.line + 1, added$2);
+ }
+
+ signalLater(doc, "change", doc, change);
+ }
+
+ // Call f for all linked documents.
+ function linkedDocs(doc, f, sharedHistOnly) {
+ function propagate(doc, skip, sharedHist) {
+ if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) {
+ var rel = doc.linked[i];
+ if (rel.doc == skip) { continue }
+ var shared = sharedHist && rel.sharedHist;
+ if (sharedHistOnly && !shared) { continue }
+ f(rel.doc, shared);
+ propagate(rel.doc, doc, shared);
+ } }
+ }
+ propagate(doc, null, true);
+ }
+
+ // Attach a document to an editor.
+ function attachDoc(cm, doc) {
+ if (doc.cm) { throw new Error("This document is already in use.") }
+ cm.doc = doc;
+ doc.cm = cm;
+ estimateLineHeights(cm);
+ loadMode(cm);
+ setDirectionClass(cm);
+ if (!cm.options.lineWrapping) { findMaxLine(cm); }
+ cm.options.mode = doc.modeOption;
+ regChange(cm);
+ }
+
+ function setDirectionClass(cm) {
+ (cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl");
+ }
+
+ function directionChanged(cm) {
+ runInOp(cm, function () {
+ setDirectionClass(cm);
+ regChange(cm);
+ });
+ }
+
+ function History(startGen) {
+ // Arrays of change events and selections. Doing something adds an
+ // event to done and clears undo. Undoing moves events from done
+ // to undone, redoing moves them in the other direction.
+ this.done = []; this.undone = [];
+ this.undoDepth = Infinity;
+ // Used to track when changes can be merged into a single undo
+ // event
+ this.lastModTime = this.lastSelTime = 0;
+ this.lastOp = this.lastSelOp = null;
+ this.lastOrigin = this.lastSelOrigin = null;
+ // Used by the isClean() method
+ this.generation = this.maxGeneration = startGen || 1;
+ }
+
+ // Create a history change event from an updateDoc-style change
+ // object.
+ function historyChangeFromChange(doc, change) {
+ var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
+ attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
+ linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true);
+ return histChange
+ }
+
+ // Pop all selection events off the end of a history array. Stop at
+ // a change event.
+ function clearSelectionEvents(array) {
+ while (array.length) {
+ var last = lst(array);
+ if (last.ranges) { array.pop(); }
+ else { break }
+ }
+ }
+
+ // Find the top change event in the history. Pop off selection
+ // events that are in the way.
+ function lastChangeEvent(hist, force) {
+ if (force) {
+ clearSelectionEvents(hist.done);
+ return lst(hist.done)
+ } else if (hist.done.length && !lst(hist.done).ranges) {
+ return lst(hist.done)
+ } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
+ hist.done.pop();
+ return lst(hist.done)
+ }
+ }
+
+ // Register a change in the history. Merges changes that are within
+ // a single operation, or are close together with an origin that
+ // allows merging (starting with "+") into a single event.
+ function addChangeToHistory(doc, change, selAfter, opId) {
+ var hist = doc.history;
+ hist.undone.length = 0;
+ var time = +new Date, cur;
+ var last;
+
+ if ((hist.lastOp == opId ||
+ hist.lastOrigin == change.origin && change.origin &&
+ ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) ||
+ change.origin.charAt(0) == "*")) &&
+ (cur = lastChangeEvent(hist, hist.lastOp == opId))) {
+ // Merge this change into the last event
+ last = lst(cur.changes);
+ if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
+ // Optimized case for simple insertion -- don't want to add
+ // new changesets for every character typed
+ last.to = changeEnd(change);
+ } else {
+ // Add new sub-event
+ cur.changes.push(historyChangeFromChange(doc, change));
+ }
+ } else {
+ // Can not be merged, start a new event.
+ var before = lst(hist.done);
+ if (!before || !before.ranges)
+ { pushSelectionToHistory(doc.sel, hist.done); }
+ cur = {changes: [historyChangeFromChange(doc, change)],
+ generation: hist.generation};
+ hist.done.push(cur);
+ while (hist.done.length > hist.undoDepth) {
+ hist.done.shift();
+ if (!hist.done[0].ranges) { hist.done.shift(); }
+ }
+ }
+ hist.done.push(selAfter);
+ hist.generation = ++hist.maxGeneration;
+ hist.lastModTime = hist.lastSelTime = time;
+ hist.lastOp = hist.lastSelOp = opId;
+ hist.lastOrigin = hist.lastSelOrigin = change.origin;
+
+ if (!last) { signal(doc, "historyAdded"); }
+ }
+
+ function selectionEventCanBeMerged(doc, origin, prev, sel) {
+ var ch = origin.charAt(0);
+ return ch == "*" ||
+ ch == "+" &&
+ prev.ranges.length == sel.ranges.length &&
+ prev.somethingSelected() == sel.somethingSelected() &&
+ new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500)
+ }
+
+ // Called whenever the selection changes, sets the new selection as
+ // the pending selection in the history, and pushes the old pending
+ // selection into the 'done' array when it was significantly
+ // different (in number of selected ranges, emptiness, or time).
+ function addSelectionToHistory(doc, sel, opId, options) {
+ var hist = doc.history, origin = options && options.origin;
+
+ // A new event is started when the previous origin does not match
+ // the current, or the origins don't allow matching. Origins
+ // starting with * are always merged, those starting with + are
+ // merged when similar and close together in time.
+ if (opId == hist.lastSelOp ||
+ (origin && hist.lastSelOrigin == origin &&
+ (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
+ selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
+ { hist.done[hist.done.length - 1] = sel; }
+ else
+ { pushSelectionToHistory(sel, hist.done); }
+
+ hist.lastSelTime = +new Date;
+ hist.lastSelOrigin = origin;
+ hist.lastSelOp = opId;
+ if (options && options.clearRedo !== false)
+ { clearSelectionEvents(hist.undone); }
+ }
+
+ function pushSelectionToHistory(sel, dest) {
+ var top = lst(dest);
+ if (!(top && top.ranges && top.equals(sel)))
+ { dest.push(sel); }
+ }
+
+ // Used to store marked span information in the history.
+ function attachLocalSpans(doc, change, from, to) {
+ var existing = change["spans_" + doc.id], n = 0;
+ doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) {
+ if (line.markedSpans)
+ { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; }
+ ++n;
+ });
+ }
+
+ // When un/re-doing restores text containing marked spans, those
+ // that have been explicitly cleared should not be restored.
+ function removeClearedSpans(spans) {
+ if (!spans) { return null }
+ var out;
+ for (var i = 0; i < spans.length; ++i) {
+ if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i); } }
+ else if (out) { out.push(spans[i]); }
+ }
+ return !out ? spans : out.length ? out : null
+ }
+
+ // Retrieve and filter the old marked spans stored in a change event.
+ function getOldSpans(doc, change) {
+ var found = change["spans_" + doc.id];
+ if (!found) { return null }
+ var nw = [];
+ for (var i = 0; i < change.text.length; ++i)
+ { nw.push(removeClearedSpans(found[i])); }
+ return nw
+ }
+
+ // Used for un/re-doing changes from the history. Combines the
+ // result of computing the existing spans with the set of spans that
+ // existed in the history (so that deleting around a span and then
+ // undoing brings back the span).
+ function mergeOldSpans(doc, change) {
+ var old = getOldSpans(doc, change);
+ var stretched = stretchSpansOverChange(doc, change);
+ if (!old) { return stretched }
+ if (!stretched) { return old }
+
+ for (var i = 0; i < old.length; ++i) {
+ var oldCur = old[i], stretchCur = stretched[i];
+ if (oldCur && stretchCur) {
+ spans: for (var j = 0; j < stretchCur.length; ++j) {
+ var span = stretchCur[j];
+ for (var k = 0; k < oldCur.length; ++k)
+ { if (oldCur[k].marker == span.marker) { continue spans } }
+ oldCur.push(span);
+ }
+ } else if (stretchCur) {
+ old[i] = stretchCur;
+ }
+ }
+ return old
+ }
+
+ // Used both to provide a JSON-safe object in .getHistory, and, when
+ // detaching a document, to split the history in two
+ function copyHistoryArray(events, newGroup, instantiateSel) {
+ var copy = [];
+ for (var i = 0; i < events.length; ++i) {
+ var event = events[i];
+ if (event.ranges) {
+ copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event);
+ continue
+ }
+ var changes = event.changes, newChanges = [];
+ copy.push({changes: newChanges});
+ for (var j = 0; j < changes.length; ++j) {
+ var change = changes[j], m = (void 0);
+ newChanges.push({from: change.from, to: change.to, text: change.text});
+ if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) {
+ if (indexOf(newGroup, Number(m[1])) > -1) {
+ lst(newChanges)[prop] = change[prop];
+ delete change[prop];
+ }
+ } } }
+ }
+ }
+ return copy
+ }
+
+ // The 'scroll' parameter given to many of these indicated whether
+ // the new cursor position should be scrolled into view after
+ // modifying the selection.
+
+ // If shift is held or the extend flag is set, extends a range to
+ // include a given position (and optionally a second position).
+ // Otherwise, simply returns the range between the given positions.
+ // Used for cursor motion and such.
+ function extendRange(range, head, other, extend) {
+ if (extend) {
+ var anchor = range.anchor;
+ if (other) {
+ var posBefore = cmp(head, anchor) < 0;
+ if (posBefore != (cmp(other, anchor) < 0)) {
+ anchor = head;
+ head = other;
+ } else if (posBefore != (cmp(head, other) < 0)) {
+ head = other;
+ }
+ }
+ return new Range(anchor, head)
+ } else {
+ return new Range(other || head, head)
+ }
+ }
+
+ // Extend the primary selection range, discard the rest.
+ function extendSelection(doc, head, other, options, extend) {
+ if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); }
+ setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options);
+ }
+
+ // Extend all selections (pos is an array of selections with length
+ // equal the number of selections)
+ function extendSelections(doc, heads, options) {
+ var out = [];
+ var extend = doc.cm && (doc.cm.display.shift || doc.extend);
+ for (var i = 0; i < doc.sel.ranges.length; i++)
+ { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); }
+ var newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex);
+ setSelection(doc, newSel, options);
+ }
+
+ // Updates a single range in the selection.
+ function replaceOneSelection(doc, i, range, options) {
+ var ranges = doc.sel.ranges.slice(0);
+ ranges[i] = range;
+ setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options);
+ }
+
+ // Reset the selection to a single range.
+ function setSimpleSelection(doc, anchor, head, options) {
+ setSelection(doc, simpleSelection(anchor, head), options);
+ }
+
+ // Give beforeSelectionChange handlers a change to influence a
+ // selection update.
+ function filterSelectionChange(doc, sel, options) {
+ var obj = {
+ ranges: sel.ranges,
+ update: function(ranges) {
+ var this$1 = this;
+
+ this.ranges = [];
+ for (var i = 0; i < ranges.length; i++)
+ { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
+ clipPos(doc, ranges[i].head)); }
+ },
+ origin: options && options.origin
+ };
+ signal(doc, "beforeSelectionChange", doc, obj);
+ if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj); }
+ if (obj.ranges != sel.ranges) { return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) }
+ else { return sel }
+ }
+
+ function setSelectionReplaceHistory(doc, sel, options) {
+ var done = doc.history.done, last = lst(done);
+ if (last && last.ranges) {
+ done[done.length - 1] = sel;
+ setSelectionNoUndo(doc, sel, options);
+ } else {
+ setSelection(doc, sel, options);
+ }
+ }
+
+ // Set a new selection.
+ function setSelection(doc, sel, options) {
+ setSelectionNoUndo(doc, sel, options);
+ addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
+ }
+
+ function setSelectionNoUndo(doc, sel, options) {
+ if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
+ { sel = filterSelectionChange(doc, sel, options); }
+
+ var bias = options && options.bias ||
+ (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
+ setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
+
+ if (!(options && options.scroll === false) && doc.cm)
+ { ensureCursorVisible(doc.cm); }
+ }
+
+ function setSelectionInner(doc, sel) {
+ if (sel.equals(doc.sel)) { return }
+
+ doc.sel = sel;
+
+ if (doc.cm) {
+ doc.cm.curOp.updateInput = 1;
+ doc.cm.curOp.selectionChanged = true;
+ signalCursorActivity(doc.cm);
+ }
+ signalLater(doc, "cursorActivity", doc);
+ }
+
+ // Verify that the selection does not partially select any atomic
+ // marked ranges.
+ function reCheckSelection(doc) {
+ setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false));
+ }
+
+ // Return a selection that does not partially select any atomic
+ // ranges.
+ function skipAtomicInSelection(doc, sel, bias, mayClear) {
+ var out;
+ for (var i = 0; i < sel.ranges.length; i++) {
+ var range = sel.ranges[i];
+ var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];
+ var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);
+ var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);
+ if (out || newAnchor != range.anchor || newHead != range.head) {
+ if (!out) { out = sel.ranges.slice(0, i); }
+ out[i] = new Range(newAnchor, newHead);
+ }
+ }
+ return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel
+ }
+
+ function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
+ var line = getLine(doc, pos.line);
+ if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {
+ var sp = line.markedSpans[i], m = sp.marker;
+
+ // Determine if we should prevent the cursor being placed to the left/right of an atomic marker
+ // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it
+ // is with selectLeft/Right
+ var preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft;
+ var preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight;
+
+ if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
+ (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
+ if (mayClear) {
+ signal(m, "beforeCursorEnter");
+ if (m.explicitlyCleared) {
+ if (!line.markedSpans) { break }
+ else {--i; continue}
+ }
+ }
+ if (!m.atomic) { continue }
+
+ if (oldPos) {
+ var near = m.find(dir < 0 ? 1 : -1), diff = (void 0);
+ if (dir < 0 ? preventCursorRight : preventCursorLeft)
+ { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); }
+ if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
+ { return skipAtomicInner(doc, near, pos, dir, mayClear) }
+ }
+
+ var far = m.find(dir < 0 ? -1 : 1);
+ if (dir < 0 ? preventCursorLeft : preventCursorRight)
+ { far = movePos(doc, far, dir, far.line == pos.line ? line : null); }
+ return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null
+ }
+ } }
+ return pos
+ }
+
+ // Ensure a given position is not inside an atomic range.
+ function skipAtomic(doc, pos, oldPos, bias, mayClear) {
+ var dir = bias || 1;
+ var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
+ (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
+ skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
+ (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));
+ if (!found) {
+ doc.cantEdit = true;
+ return Pos(doc.first, 0)
+ }
+ return found
+ }
+
+ function movePos(doc, pos, dir, line) {
+ if (dir < 0 && pos.ch == 0) {
+ if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) }
+ else { return null }
+ } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
+ if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) }
+ else { return null }
+ } else {
+ return new Pos(pos.line, pos.ch + dir)
+ }
+ }
+
+ function selectAll(cm) {
+ cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);
+ }
+
+ // UPDATING
+
+ // Allow "beforeChange" event handlers to influence a change
+ function filterChange(doc, change, update) {
+ var obj = {
+ canceled: false,
+ from: change.from,
+ to: change.to,
+ text: change.text,
+ origin: change.origin,
+ cancel: function () { return obj.canceled = true; }
+ };
+ if (update) { obj.update = function (from, to, text, origin) {
+ if (from) { obj.from = clipPos(doc, from); }
+ if (to) { obj.to = clipPos(doc, to); }
+ if (text) { obj.text = text; }
+ if (origin !== undefined) { obj.origin = origin; }
+ }; }
+ signal(doc, "beforeChange", doc, obj);
+ if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj); }
+
+ if (obj.canceled) {
+ if (doc.cm) { doc.cm.curOp.updateInput = 2; }
+ return null
+ }
+ return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}
+ }
+
+ // Apply a change to a document, and add it to the document's
+ // history, and propagating it to all linked documents.
+ function makeChange(doc, change, ignoreReadOnly) {
+ if (doc.cm) {
+ if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) }
+ if (doc.cm.state.suppressEdits) { return }
+ }
+
+ if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
+ change = filterChange(doc, change, true);
+ if (!change) { return }
+ }
+
+ // Possibly split or suppress the update based on the presence
+ // of read-only spans in its range.
+ var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
+ if (split) {
+ for (var i = split.length - 1; i >= 0; --i)
+ { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}); }
+ } else {
+ makeChangeInner(doc, change);
+ }
+ }
+
+ function makeChangeInner(doc, change) {
+ if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return }
+ var selAfter = computeSelAfterChange(doc, change);
+ addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
+
+ makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
+ var rebased = [];
+
+ linkedDocs(doc, function (doc, sharedHist) {
+ if (!sharedHist && indexOf(rebased, doc.history) == -1) {
+ rebaseHist(doc.history, change);
+ rebased.push(doc.history);
+ }
+ makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
+ });
+ }
+
+ // Revert a change stored in a document's history.
+ function makeChangeFromHistory(doc, type, allowSelectionOnly) {
+ var suppress = doc.cm && doc.cm.state.suppressEdits;
+ if (suppress && !allowSelectionOnly) { return }
+
+ var hist = doc.history, event, selAfter = doc.sel;
+ var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done;
+
+ // Verify that there is a useable event (so that ctrl-z won't
+ // needlessly clear selection events)
+ var i = 0;
+ for (; i < source.length; i++) {
+ event = source[i];
+ if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)
+ { break }
+ }
+ if (i == source.length) { return }
+ hist.lastOrigin = hist.lastSelOrigin = null;
+
+ for (;;) {
+ event = source.pop();
+ if (event.ranges) {
+ pushSelectionToHistory(event, dest);
+ if (allowSelectionOnly && !event.equals(doc.sel)) {
+ setSelection(doc, event, {clearRedo: false});
+ return
+ }
+ selAfter = event;
+ } else if (suppress) {
+ source.push(event);
+ return
+ } else { break }
+ }
+
+ // Build up a reverse change object to add to the opposite history
+ // stack (redo when undoing, and vice versa).
+ var antiChanges = [];
+ pushSelectionToHistory(selAfter, dest);
+ dest.push({changes: antiChanges, generation: hist.generation});
+ hist.generation = event.generation || ++hist.maxGeneration;
+
+ var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
+
+ var loop = function ( i ) {
+ var change = event.changes[i];
+ change.origin = type;
+ if (filter && !filterChange(doc, change, false)) {
+ source.length = 0;
+ return {}
+ }
+
+ antiChanges.push(historyChangeFromChange(doc, change));
+
+ var after = i ? computeSelAfterChange(doc, change) : lst(source);
+ makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
+ if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); }
+ var rebased = [];
+
+ // Propagate to the linked documents
+ linkedDocs(doc, function (doc, sharedHist) {
+ if (!sharedHist && indexOf(rebased, doc.history) == -1) {
+ rebaseHist(doc.history, change);
+ rebased.push(doc.history);
+ }
+ makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
+ });
+ };
+
+ for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) {
+ var returned = loop( i$1 );
+
+ if ( returned ) return returned.v;
+ }
+ }
+
+ // Sub-views need their line numbers shifted when text is added
+ // above or below them in the parent document.
+ function shiftDoc(doc, distance) {
+ if (distance == 0) { return }
+ doc.first += distance;
+ doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range(
+ Pos(range.anchor.line + distance, range.anchor.ch),
+ Pos(range.head.line + distance, range.head.ch)
+ ); }), doc.sel.primIndex);
+ if (doc.cm) {
+ regChange(doc.cm, doc.first, doc.first - distance, distance);
+ for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
+ { regLineChange(doc.cm, l, "gutter"); }
+ }
+ }
+
+ // More lower-level change function, handling only a single document
+ // (not linked ones).
+ function makeChangeSingleDoc(doc, change, selAfter, spans) {
+ if (doc.cm && !doc.cm.curOp)
+ { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) }
+
+ if (change.to.line < doc.first) {
+ shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
+ return
+ }
+ if (change.from.line > doc.lastLine()) { return }
+
+ // Clip the change to the size of this doc
+ if (change.from.line < doc.first) {
+ var shift = change.text.length - 1 - (doc.first - change.from.line);
+ shiftDoc(doc, shift);
+ change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
+ text: [lst(change.text)], origin: change.origin};
+ }
+ var last = doc.lastLine();
+ if (change.to.line > last) {
+ change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
+ text: [change.text[0]], origin: change.origin};
+ }
+
+ change.removed = getBetween(doc, change.from, change.to);
+
+ if (!selAfter) { selAfter = computeSelAfterChange(doc, change); }
+ if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); }
+ else { updateDoc(doc, change, spans); }
+ setSelectionNoUndo(doc, selAfter, sel_dontScroll);
+
+ if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0)))
+ { doc.cantEdit = false; }
+ }
+
+ // Handle the interaction of a change to a document with the editor
+ // that this document is part of.
+ function makeChangeSingleDocInEditor(cm, change, spans) {
+ var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
+
+ var recomputeMaxLength = false, checkWidthStart = from.line;
+ if (!cm.options.lineWrapping) {
+ checkWidthStart = lineNo(visualLine(getLine(doc, from.line)));
+ doc.iter(checkWidthStart, to.line + 1, function (line) {
+ if (line == display.maxLine) {
+ recomputeMaxLength = true;
+ return true
+ }
+ });
+ }
+
+ if (doc.sel.contains(change.from, change.to) > -1)
+ { signalCursorActivity(cm); }
+
+ updateDoc(doc, change, spans, estimateHeight(cm));
+
+ if (!cm.options.lineWrapping) {
+ doc.iter(checkWidthStart, from.line + change.text.length, function (line) {
+ var len = lineLength(line);
+ if (len > display.maxLineLength) {
+ display.maxLine = line;
+ display.maxLineLength = len;
+ display.maxLineChanged = true;
+ recomputeMaxLength = false;
+ }
+ });
+ if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; }
+ }
+
+ retreatFrontier(doc, from.line);
+ startWorker(cm, 400);
+
+ var lendiff = change.text.length - (to.line - from.line) - 1;
+ // Remember that these lines changed, for updating the display
+ if (change.full)
+ { regChange(cm); }
+ else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
+ { regLineChange(cm, from.line, "text"); }
+ else
+ { regChange(cm, from.line, to.line + 1, lendiff); }
+
+ var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change");
+ if (changeHandler || changesHandler) {
+ var obj = {
+ from: from, to: to,
+ text: change.text,
+ removed: change.removed,
+ origin: change.origin
+ };
+ if (changeHandler) { signalLater(cm, "change", cm, obj); }
+ if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); }
+ }
+ cm.display.selForContextMenu = null;
+ }
+
+ function replaceRange(doc, code, from, to, origin) {
+ var assign;
+
+ if (!to) { to = from; }
+ if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); }
+ if (typeof code == "string") { code = doc.splitLines(code); }
+ makeChange(doc, {from: from, to: to, text: code, origin: origin});
+ }
+
+ // Rebasing/resetting history to deal with externally-sourced changes
+
+ function rebaseHistSelSingle(pos, from, to, diff) {
+ if (to < pos.line) {
+ pos.line += diff;
+ } else if (from < pos.line) {
+ pos.line = from;
+ pos.ch = 0;
+ }
+ }
+
+ // Tries to rebase an array of history events given a change in the
+ // document. If the change touches the same lines as the event, the
+ // event, and everything 'behind' it, is discarded. If the change is
+ // before the event, the event's positions are updated. Uses a
+ // copy-on-write scheme for the positions, to avoid having to
+ // reallocate them all on every rebase, but also avoid problems with
+ // shared position objects being unsafely updated.
+ function rebaseHistArray(array, from, to, diff) {
+ for (var i = 0; i < array.length; ++i) {
+ var sub = array[i], ok = true;
+ if (sub.ranges) {
+ if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }
+ for (var j = 0; j < sub.ranges.length; j++) {
+ rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);
+ rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);
+ }
+ continue
+ }
+ for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) {
+ var cur = sub.changes[j$1];
+ if (to < cur.from.line) {
+ cur.from = Pos(cur.from.line + diff, cur.from.ch);
+ cur.to = Pos(cur.to.line + diff, cur.to.ch);
+ } else if (from <= cur.to.line) {
+ ok = false;
+ break
+ }
+ }
+ if (!ok) {
+ array.splice(0, i + 1);
+ i = 0;
+ }
+ }
+ }
+
+ function rebaseHist(hist, change) {
+ var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
+ rebaseHistArray(hist.done, from, to, diff);
+ rebaseHistArray(hist.undone, from, to, diff);
+ }
+
+ // Utility for applying a change to a line by handle or number,
+ // returning the number and optionally registering the line as
+ // changed.
+ function changeLine(doc, handle, changeType, op) {
+ var no = handle, line = handle;
+ if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)); }
+ else { no = lineNo(handle); }
+ if (no == null) { return null }
+ if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); }
+ return line
+ }
+
+ // The document is represented as a BTree consisting of leaves, with
+ // chunk of lines in them, and branches, with up to ten leaves or
+ // other branch nodes below them. The top node is always a branch
+ // node, and is the document object itself (meaning it has
+ // additional methods and properties).
+ //
+ // All nodes have parent links. The tree is used both to go from
+ // line numbers to line objects, and to go from objects to numbers.
+ // It also indexes by height, and is used to convert between height
+ // and line object, and to find the total height of the document.
+ //
+ // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
+
+ function LeafChunk(lines) {
+ var this$1 = this;
+
+ this.lines = lines;
+ this.parent = null;
+ var height = 0;
+ for (var i = 0; i < lines.length; ++i) {
+ lines[i].parent = this$1;
+ height += lines[i].height;
+ }
+ this.height = height;
+ }
+
+ LeafChunk.prototype = {
+ chunkSize: function() { return this.lines.length },
+
+ // Remove the n lines at offset 'at'.
+ removeInner: function(at, n) {
+ var this$1 = this;
+
+ for (var i = at, e = at + n; i < e; ++i) {
+ var line = this$1.lines[i];
+ this$1.height -= line.height;
+ cleanUpLine(line);
+ signalLater(line, "delete");
+ }
+ this.lines.splice(at, n);
+ },
+
+ // Helper used to collapse a small branch into a single leaf.
+ collapse: function(lines) {
+ lines.push.apply(lines, this.lines);
+ },
+
+ // Insert the given array of lines at offset 'at', count them as
+ // having the given height.
+ insertInner: function(at, lines, height) {
+ var this$1 = this;
+
+ this.height += height;
+ this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
+ for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1; }
+ },
+
+ // Used to iterate over a part of the tree.
+ iterN: function(at, n, op) {
+ var this$1 = this;
+
+ for (var e = at + n; at < e; ++at)
+ { if (op(this$1.lines[at])) { return true } }
+ }
+ };
+
+ function BranchChunk(children) {
+ var this$1 = this;
+
+ this.children = children;
+ var size = 0, height = 0;
+ for (var i = 0; i < children.length; ++i) {
+ var ch = children[i];
+ size += ch.chunkSize(); height += ch.height;
+ ch.parent = this$1;
+ }
+ this.size = size;
+ this.height = height;
+ this.parent = null;
+ }
+
+ BranchChunk.prototype = {
+ chunkSize: function() { return this.size },
+
+ removeInner: function(at, n) {
+ var this$1 = this;
+
+ this.size -= n;
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this$1.children[i], sz = child.chunkSize();
+ if (at < sz) {
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
+ child.removeInner(at, rm);
+ this$1.height -= oldHeight - child.height;
+ if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null; }
+ if ((n -= rm) == 0) { break }
+ at = 0;
+ } else { at -= sz; }
+ }
+ // If the result is smaller than 25 lines, ensure that it is a
+ // single leaf node.
+ if (this.size - n < 25 &&
+ (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
+ var lines = [];
+ this.collapse(lines);
+ this.children = [new LeafChunk(lines)];
+ this.children[0].parent = this;
+ }
+ },
+
+ collapse: function(lines) {
+ var this$1 = this;
+
+ for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines); }
+ },
+
+ insertInner: function(at, lines, height) {
+ var this$1 = this;
+
+ this.size += lines.length;
+ this.height += height;
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this$1.children[i], sz = child.chunkSize();
+ if (at <= sz) {
+ child.insertInner(at, lines, height);
+ if (child.lines && child.lines.length > 50) {
+ // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.
+ // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.
+ var remaining = child.lines.length % 25 + 25;
+ for (var pos = remaining; pos < child.lines.length;) {
+ var leaf = new LeafChunk(child.lines.slice(pos, pos += 25));
+ child.height -= leaf.height;
+ this$1.children.splice(++i, 0, leaf);
+ leaf.parent = this$1;
+ }
+ child.lines = child.lines.slice(0, remaining);
+ this$1.maybeSpill();
+ }
+ break
+ }
+ at -= sz;
+ }
+ },
+
+ // When a node has grown, check whether it should be split.
+ maybeSpill: function() {
+ if (this.children.length <= 10) { return }
+ var me = this;
+ do {
+ var spilled = me.children.splice(me.children.length - 5, 5);
+ var sibling = new BranchChunk(spilled);
+ if (!me.parent) { // Become the parent node
+ var copy = new BranchChunk(me.children);
+ copy.parent = me;
+ me.children = [copy, sibling];
+ me = copy;
+ } else {
+ me.size -= sibling.size;
+ me.height -= sibling.height;
+ var myIndex = indexOf(me.parent.children, me);
+ me.parent.children.splice(myIndex + 1, 0, sibling);
+ }
+ sibling.parent = me.parent;
+ } while (me.children.length > 10)
+ me.parent.maybeSpill();
+ },
+
+ iterN: function(at, n, op) {
+ var this$1 = this;
+
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this$1.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; }
+ }
+ }
+ };
+
+ // Line widgets are block elements displayed above or below a line.
+
+ var LineWidget = function(doc, node, options) {
+ var this$1 = this;
+
+ if (options) { for (var opt in options) { if (options.hasOwnProperty(opt))
+ { this$1[opt] = options[opt]; } } }
+ this.doc = doc;
+ this.node = node;
+ };
+
+ LineWidget.prototype.clear = function () {
+ var this$1 = this;
+
+ var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
+ if (no == null || !ws) { return }
+ for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1); } }
+ if (!ws.length) { line.widgets = null; }
+ var height = widgetHeight(this);
+ updateLineHeight(line, Math.max(0, line.height - height));
+ if (cm) {
+ runInOp(cm, function () {
+ adjustScrollWhenAboveVisible(cm, line, -height);
+ regLineChange(cm, no, "widget");
+ });
+ signalLater(cm, "lineWidgetCleared", cm, this, no);
+ }
+ };
+
+ LineWidget.prototype.changed = function () {
+ var this$1 = this;
+
+ var oldH = this.height, cm = this.doc.cm, line = this.line;
+ this.height = null;
+ var diff = widgetHeight(this) - oldH;
+ if (!diff) { return }
+ if (!lineIsHidden(this.doc, line)) { updateLineHeight(line, line.height + diff); }
+ if (cm) {
+ runInOp(cm, function () {
+ cm.curOp.forceUpdate = true;
+ adjustScrollWhenAboveVisible(cm, line, diff);
+ signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line));
+ });
+ }
+ };
+ eventMixin(LineWidget);
+
+ function adjustScrollWhenAboveVisible(cm, line, diff) {
+ if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
+ { addToScrollTop(cm, diff); }
+ }
+
+ function addLineWidget(doc, handle, node, options) {
+ var widget = new LineWidget(doc, node, options);
+ var cm = doc.cm;
+ if (cm && widget.noHScroll) { cm.display.alignWidgets = true; }
+ changeLine(doc, handle, "widget", function (line) {
+ var widgets = line.widgets || (line.widgets = []);
+ if (widget.insertAt == null) { widgets.push(widget); }
+ else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); }
+ widget.line = line;
+ if (cm && !lineIsHidden(doc, line)) {
+ var aboveVisible = heightAtLine(line) < doc.scrollTop;
+ updateLineHeight(line, line.height + widgetHeight(widget));
+ if (aboveVisible) { addToScrollTop(cm, widget.height); }
+ cm.curOp.forceUpdate = true;
+ }
+ return true
+ });
+ if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)); }
+ return widget
+ }
+
+ // TEXTMARKERS
+
+ // Created with markText and setBookmark methods. A TextMarker is a
+ // handle that can be used to clear or find a marked position in the
+ // document. Line objects hold arrays (markedSpans) containing
+ // {from, to, marker} object pointing to such marker objects, and
+ // indicating that such a marker is present on that line. Multiple
+ // lines may point to the same marker when it spans across lines.
+ // The spans will have null for their from/to properties when the
+ // marker continues beyond the start/end of the line. Markers have
+ // links back to the lines they currently touch.
+
+ // Collapsed markers have unique ids, in order to be able to order
+ // them, which is needed for uniquely determining an outer marker
+ // when they overlap (they may nest, but not partially overlap).
+ var nextMarkerId = 0;
+
+ var TextMarker = function(doc, type) {
+ this.lines = [];
+ this.type = type;
+ this.doc = doc;
+ this.id = ++nextMarkerId;
+ };
+
+ // Clear the marker.
+ TextMarker.prototype.clear = function () {
+ var this$1 = this;
+
+ if (this.explicitlyCleared) { return }
+ var cm = this.doc.cm, withOp = cm && !cm.curOp;
+ if (withOp) { startOperation(cm); }
+ if (hasHandler(this, "clear")) {
+ var found = this.find();
+ if (found) { signalLater(this, "clear", found.from, found.to); }
+ }
+ var min = null, max = null;
+ for (var i = 0; i < this.lines.length; ++i) {
+ var line = this$1.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this$1);
+ if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text"); }
+ else if (cm) {
+ if (span.to != null) { max = lineNo(line); }
+ if (span.from != null) { min = lineNo(line); }
+ }
+ line.markedSpans = removeMarkedSpan(line.markedSpans, span);
+ if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm)
+ { updateLineHeight(line, textHeight(cm.display)); }
+ }
+ if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) {
+ var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual);
+ if (len > cm.display.maxLineLength) {
+ cm.display.maxLine = visual;
+ cm.display.maxLineLength = len;
+ cm.display.maxLineChanged = true;
+ }
+ } }
+
+ if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1); }
+ this.lines.length = 0;
+ this.explicitlyCleared = true;
+ if (this.atomic && this.doc.cantEdit) {
+ this.doc.cantEdit = false;
+ if (cm) { reCheckSelection(cm.doc); }
+ }
+ if (cm) { signalLater(cm, "markerCleared", cm, this, min, max); }
+ if (withOp) { endOperation(cm); }
+ if (this.parent) { this.parent.clear(); }
+ };
+
+ // Find the position of the marker in the document. Returns a {from,
+ // to} object by default. Side can be passed to get a specific side
+ // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
+ // Pos objects returned contain a line object, rather than a line
+ // number (used to prevent looking up the same line twice).
+ TextMarker.prototype.find = function (side, lineObj) {
+ var this$1 = this;
+
+ if (side == null && this.type == "bookmark") { side = 1; }
+ var from, to;
+ for (var i = 0; i < this.lines.length; ++i) {
+ var line = this$1.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this$1);
+ if (span.from != null) {
+ from = Pos(lineObj ? line : lineNo(line), span.from);
+ if (side == -1) { return from }
+ }
+ if (span.to != null) {
+ to = Pos(lineObj ? line : lineNo(line), span.to);
+ if (side == 1) { return to }
+ }
+ }
+ return from && {from: from, to: to}
+ };
+
+ // Signals that the marker's widget changed, and surrounding layout
+ // should be recomputed.
+ TextMarker.prototype.changed = function () {
+ var this$1 = this;
+
+ var pos = this.find(-1, true), widget = this, cm = this.doc.cm;
+ if (!pos || !cm) { return }
+ runInOp(cm, function () {
+ var line = pos.line, lineN = lineNo(pos.line);
+ var view = findViewForLine(cm, lineN);
+ if (view) {
+ clearLineMeasurementCacheFor(view);
+ cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;
+ }
+ cm.curOp.updateMaxLine = true;
+ if (!lineIsHidden(widget.doc, line) && widget.height != null) {
+ var oldHeight = widget.height;
+ widget.height = null;
+ var dHeight = widgetHeight(widget) - oldHeight;
+ if (dHeight)
+ { updateLineHeight(line, line.height + dHeight); }
+ }
+ signalLater(cm, "markerChanged", cm, this$1);
+ });
+ };
+
+ TextMarker.prototype.attachLine = function (line) {
+ if (!this.lines.length && this.doc.cm) {
+ var op = this.doc.cm.curOp;
+ if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
+ { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); }
+ }
+ this.lines.push(line);
+ };
+
+ TextMarker.prototype.detachLine = function (line) {
+ this.lines.splice(indexOf(this.lines, line), 1);
+ if (!this.lines.length && this.doc.cm) {
+ var op = this.doc.cm.curOp
+ ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
+ }
+ };
+ eventMixin(TextMarker);
+
+ // Create a marker, wire it up to the right lines, and
+ function markText(doc, from, to, options, type) {
+ // Shared markers (across linked documents) are handled separately
+ // (markTextShared will call out to this again, once per
+ // document).
+ if (options && options.shared) { return markTextShared(doc, from, to, options, type) }
+ // Ensure we are in an operation.
+ if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) }
+
+ var marker = new TextMarker(doc, type), diff = cmp(from, to);
+ if (options) { copyObj(options, marker, false); }
+ // Don't connect empty markers unless clearWhenEmpty is false
+ if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
+ { return marker }
+ if (marker.replacedWith) {
+ // Showing up as a widget implies collapsed (widget replaces text)
+ marker.collapsed = true;
+ marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget");
+ if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true"); }
+ if (options.insertLeft) { marker.widgetNode.insertLeft = true; }
+ }
+ if (marker.collapsed) {
+ if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
+ from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
+ { throw new Error("Inserting collapsed marker partially overlapping an existing one") }
+ seeCollapsedSpans();
+ }
+
+ if (marker.addToHistory)
+ { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); }
+
+ var curLine = from.line, cm = doc.cm, updateMaxLine;
+ doc.iter(curLine, to.line + 1, function (line) {
+ if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
+ { updateMaxLine = true; }
+ if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); }
+ addMarkedSpan(line, new MarkedSpan(marker,
+ curLine == from.line ? from.ch : null,
+ curLine == to.line ? to.ch : null));
+ ++curLine;
+ });
+ // lineIsHidden depends on the presence of the spans, so needs a second pass
+ if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) {
+ if (lineIsHidden(doc, line)) { updateLineHeight(line, 0); }
+ }); }
+
+ if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }); }
+
+ if (marker.readOnly) {
+ seeReadOnlySpans();
+ if (doc.history.done.length || doc.history.undone.length)
+ { doc.clearHistory(); }
+ }
+ if (marker.collapsed) {
+ marker.id = ++nextMarkerId;
+ marker.atomic = true;
+ }
+ if (cm) {
+ // Sync editor state
+ if (updateMaxLine) { cm.curOp.updateMaxLine = true; }
+ if (marker.collapsed)
+ { regChange(cm, from.line, to.line + 1); }
+ else if (marker.className || marker.startStyle || marker.endStyle || marker.css ||
+ marker.attributes || marker.title)
+ { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text"); } }
+ if (marker.atomic) { reCheckSelection(cm.doc); }
+ signalLater(cm, "markerAdded", cm, marker);
+ }
+ return marker
+ }
+
+ // SHARED TEXTMARKERS
+
+ // A shared marker spans multiple linked documents. It is
+ // implemented as a meta-marker-object controlling multiple normal
+ // markers.
+ var SharedTextMarker = function(markers, primary) {
+ var this$1 = this;
+
+ this.markers = markers;
+ this.primary = primary;
+ for (var i = 0; i < markers.length; ++i)
+ { markers[i].parent = this$1; }
+ };
+
+ SharedTextMarker.prototype.clear = function () {
+ var this$1 = this;
+
+ if (this.explicitlyCleared) { return }
+ this.explicitlyCleared = true;
+ for (var i = 0; i < this.markers.length; ++i)
+ { this$1.markers[i].clear(); }
+ signalLater(this, "clear");
+ };
+
+ SharedTextMarker.prototype.find = function (side, lineObj) {
+ return this.primary.find(side, lineObj)
+ };
+ eventMixin(SharedTextMarker);
+
+ function markTextShared(doc, from, to, options, type) {
+ options = copyObj(options);
+ options.shared = false;
+ var markers = [markText(doc, from, to, options, type)], primary = markers[0];
+ var widget = options.widgetNode;
+ linkedDocs(doc, function (doc) {
+ if (widget) { options.widgetNode = widget.cloneNode(true); }
+ markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
+ for (var i = 0; i < doc.linked.length; ++i)
+ { if (doc.linked[i].isParent) { return } }
+ primary = lst(markers);
+ });
+ return new SharedTextMarker(markers, primary)
+ }
+
+ function findSharedMarkers(doc) {
+ return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; })
+ }
+
+ function copySharedMarkers(doc, markers) {
+ for (var i = 0; i < markers.length; i++) {
+ var marker = markers[i], pos = marker.find();
+ var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);
+ if (cmp(mFrom, mTo)) {
+ var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type);
+ marker.markers.push(subMark);
+ subMark.parent = marker;
+ }
+ }
+ }
+
+ function detachSharedMarkers(markers) {
+ var loop = function ( i ) {
+ var marker = markers[i], linked = [marker.primary.doc];
+ linkedDocs(marker.primary.doc, function (d) { return linked.push(d); });
+ for (var j = 0; j < marker.markers.length; j++) {
+ var subMarker = marker.markers[j];
+ if (indexOf(linked, subMarker.doc) == -1) {
+ subMarker.parent = null;
+ marker.markers.splice(j--, 1);
+ }
+ }
+ };
+
+ for (var i = 0; i < markers.length; i++) loop( i );
+ }
+
+ var nextDocId = 0;
+ var Doc = function(text, mode, firstLine, lineSep, direction) {
+ if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) }
+ if (firstLine == null) { firstLine = 0; }
+
+ BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
+ this.first = firstLine;
+ this.scrollTop = this.scrollLeft = 0;
+ this.cantEdit = false;
+ this.cleanGeneration = 1;
+ this.modeFrontier = this.highlightFrontier = firstLine;
+ var start = Pos(firstLine, 0);
+ this.sel = simpleSelection(start);
+ this.history = new History(null);
+ this.id = ++nextDocId;
+ this.modeOption = mode;
+ this.lineSep = lineSep;
+ this.direction = (direction == "rtl") ? "rtl" : "ltr";
+ this.extend = false;
+
+ if (typeof text == "string") { text = this.splitLines(text); }
+ updateDoc(this, {from: start, to: start, text: text});
+ setSelection(this, simpleSelection(start), sel_dontScroll);
+ };
+
+ Doc.prototype = createObj(BranchChunk.prototype, {
+ constructor: Doc,
+ // Iterate over the document. Supports two forms -- with only one
+ // argument, it calls that for each line in the document. With
+ // three, it iterates over the range given by the first two (with
+ // the second being non-inclusive).
+ iter: function(from, to, op) {
+ if (op) { this.iterN(from - this.first, to - from, op); }
+ else { this.iterN(this.first, this.first + this.size, from); }
+ },
+
+ // Non-public interface for adding and removing lines.
+ insert: function(at, lines) {
+ var height = 0;
+ for (var i = 0; i < lines.length; ++i) { height += lines[i].height; }
+ this.insertInner(at - this.first, lines, height);
+ },
+ remove: function(at, n) { this.removeInner(at - this.first, n); },
+
+ // From here, the methods are part of the public interface. Most
+ // are also available from CodeMirror (editor) instances.
+
+ getValue: function(lineSep) {
+ var lines = getLines(this, this.first, this.first + this.size);
+ if (lineSep === false) { return lines }
+ return lines.join(lineSep || this.lineSeparator())
+ },
+ setValue: docMethodOp(function(code) {
+ var top = Pos(this.first, 0), last = this.first + this.size - 1;
+ makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
+ text: this.splitLines(code), origin: "setValue", full: true}, true);
+ if (this.cm) { scrollToCoords(this.cm, 0, 0); }
+ setSelection(this, simpleSelection(top), sel_dontScroll);
+ }),
+ replaceRange: function(code, from, to, origin) {
+ from = clipPos(this, from);
+ to = to ? clipPos(this, to) : from;
+ replaceRange(this, code, from, to, origin);
+ },
+ getRange: function(from, to, lineSep) {
+ var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
+ if (lineSep === false) { return lines }
+ return lines.join(lineSep || this.lineSeparator())
+ },
+
+ getLine: function(line) {var l = this.getLineHandle(line); return l && l.text},
+
+ getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }},
+ getLineNumber: function(line) {return lineNo(line)},
+
+ getLineHandleVisualStart: function(line) {
+ if (typeof line == "number") { line = getLine(this, line); }
+ return visualLine(line)
+ },
+
+ lineCount: function() {return this.size},
+ firstLine: function() {return this.first},
+ lastLine: function() {return this.first + this.size - 1},
+
+ clipPos: function(pos) {return clipPos(this, pos)},
+
+ getCursor: function(start) {
+ var range$$1 = this.sel.primary(), pos;
+ if (start == null || start == "head") { pos = range$$1.head; }
+ else if (start == "anchor") { pos = range$$1.anchor; }
+ else if (start == "end" || start == "to" || start === false) { pos = range$$1.to(); }
+ else { pos = range$$1.from(); }
+ return pos
+ },
+ listSelections: function() { return this.sel.ranges },
+ somethingSelected: function() {return this.sel.somethingSelected()},
+
+ setCursor: docMethodOp(function(line, ch, options) {
+ setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options);
+ }),
+ setSelection: docMethodOp(function(anchor, head, options) {
+ setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options);
+ }),
+ extendSelection: docMethodOp(function(head, other, options) {
+ extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
+ }),
+ extendSelections: docMethodOp(function(heads, options) {
+ extendSelections(this, clipPosArray(this, heads), options);
+ }),
+ extendSelectionsBy: docMethodOp(function(f, options) {
+ var heads = map(this.sel.ranges, f);
+ extendSelections(this, clipPosArray(this, heads), options);
+ }),
+ setSelections: docMethodOp(function(ranges, primary, options) {
+ var this$1 = this;
+
+ if (!ranges.length) { return }
+ var out = [];
+ for (var i = 0; i < ranges.length; i++)
+ { out[i] = new Range(clipPos(this$1, ranges[i].anchor),
+ clipPos(this$1, ranges[i].head)); }
+ if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); }
+ setSelection(this, normalizeSelection(this.cm, out, primary), options);
+ }),
+ addSelection: docMethodOp(function(anchor, head, options) {
+ var ranges = this.sel.ranges.slice(0);
+ ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)));
+ setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options);
+ }),
+
+ getSelection: function(lineSep) {
+ var this$1 = this;
+
+ var ranges = this.sel.ranges, lines;
+ for (var i = 0; i < ranges.length; i++) {
+ var sel = getBetween(this$1, ranges[i].from(), ranges[i].to());
+ lines = lines ? lines.concat(sel) : sel;
+ }
+ if (lineSep === false) { return lines }
+ else { return lines.join(lineSep || this.lineSeparator()) }
+ },
+ getSelections: function(lineSep) {
+ var this$1 = this;
+
+ var parts = [], ranges = this.sel.ranges;
+ for (var i = 0; i < ranges.length; i++) {
+ var sel = getBetween(this$1, ranges[i].from(), ranges[i].to());
+ if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()); }
+ parts[i] = sel;
+ }
+ return parts
+ },
+ replaceSelection: function(code, collapse, origin) {
+ var dup = [];
+ for (var i = 0; i < this.sel.ranges.length; i++)
+ { dup[i] = code; }
+ this.replaceSelections(dup, collapse, origin || "+input");
+ },
+ replaceSelections: docMethodOp(function(code, collapse, origin) {
+ var this$1 = this;
+
+ var changes = [], sel = this.sel;
+ for (var i = 0; i < sel.ranges.length; i++) {
+ var range$$1 = sel.ranges[i];
+ changes[i] = {from: range$$1.from(), to: range$$1.to(), text: this$1.splitLines(code[i]), origin: origin};
+ }
+ var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse);
+ for (var i$1 = changes.length - 1; i$1 >= 0; i$1--)
+ { makeChange(this$1, changes[i$1]); }
+ if (newSel) { setSelectionReplaceHistory(this, newSel); }
+ else if (this.cm) { ensureCursorVisible(this.cm); }
+ }),
+ undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
+ redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
+ undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
+ redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}),
+
+ setExtending: function(val) {this.extend = val;},
+ getExtending: function() {return this.extend},
+
+ historySize: function() {
+ var hist = this.history, done = 0, undone = 0;
+ for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } }
+ for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } }
+ return {undo: done, redo: undone}
+ },
+ clearHistory: function() {this.history = new History(this.history.maxGeneration);},
+
+ markClean: function() {
+ this.cleanGeneration = this.changeGeneration(true);
+ },
+ changeGeneration: function(forceSplit) {
+ if (forceSplit)
+ { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; }
+ return this.history.generation
+ },
+ isClean: function (gen) {
+ return this.history.generation == (gen || this.cleanGeneration)
+ },
+
+ getHistory: function() {
+ return {done: copyHistoryArray(this.history.done),
+ undone: copyHistoryArray(this.history.undone)}
+ },
+ setHistory: function(histData) {
+ var hist = this.history = new History(this.history.maxGeneration);
+ hist.done = copyHistoryArray(histData.done.slice(0), null, true);
+ hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);
+ },
+
+ setGutterMarker: docMethodOp(function(line, gutterID, value) {
+ return changeLine(this, line, "gutter", function (line) {
+ var markers = line.gutterMarkers || (line.gutterMarkers = {});
+ markers[gutterID] = value;
+ if (!value && isEmpty(markers)) { line.gutterMarkers = null; }
+ return true
+ })
+ }),
+
+ clearGutter: docMethodOp(function(gutterID) {
+ var this$1 = this;
+
+ this.iter(function (line) {
+ if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
+ changeLine(this$1, line, "gutter", function () {
+ line.gutterMarkers[gutterID] = null;
+ if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; }
+ return true
+ });
+ }
+ });
+ }),
+
+ lineInfo: function(line) {
+ var n;
+ if (typeof line == "number") {
+ if (!isLine(this, line)) { return null }
+ n = line;
+ line = getLine(this, line);
+ if (!line) { return null }
+ } else {
+ n = lineNo(line);
+ if (n == null) { return null }
+ }
+ return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
+ textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
+ widgets: line.widgets}
+ },
+
+ addLineClass: docMethodOp(function(handle, where, cls) {
+ return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) {
+ var prop = where == "text" ? "textClass"
+ : where == "background" ? "bgClass"
+ : where == "gutter" ? "gutterClass" : "wrapClass";
+ if (!line[prop]) { line[prop] = cls; }
+ else if (classTest(cls).test(line[prop])) { return false }
+ else { line[prop] += " " + cls; }
+ return true
+ })
+ }),
+ removeLineClass: docMethodOp(function(handle, where, cls) {
+ return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) {
+ var prop = where == "text" ? "textClass"
+ : where == "background" ? "bgClass"
+ : where == "gutter" ? "gutterClass" : "wrapClass";
+ var cur = line[prop];
+ if (!cur) { return false }
+ else if (cls == null) { line[prop] = null; }
+ else {
+ var found = cur.match(classTest(cls));
+ if (!found) { return false }
+ var end = found.index + found[0].length;
+ line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
+ }
+ return true
+ })
+ }),
+
+ addLineWidget: docMethodOp(function(handle, node, options) {
+ return addLineWidget(this, handle, node, options)
+ }),
+ removeLineWidget: function(widget) { widget.clear(); },
+
+ markText: function(from, to, options) {
+ return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range")
+ },
+ setBookmark: function(pos, options) {
+ var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
+ insertLeft: options && options.insertLeft,
+ clearWhenEmpty: false, shared: options && options.shared,
+ handleMouseEvents: options && options.handleMouseEvents};
+ pos = clipPos(this, pos);
+ return markText(this, pos, pos, realOpts, "bookmark")
+ },
+ findMarksAt: function(pos) {
+ pos = clipPos(this, pos);
+ var markers = [], spans = getLine(this, pos.line).markedSpans;
+ if (spans) { for (var i = 0; i < spans.length; ++i) {
+ var span = spans[i];
+ if ((span.from == null || span.from <= pos.ch) &&
+ (span.to == null || span.to >= pos.ch))
+ { markers.push(span.marker.parent || span.marker); }
+ } }
+ return markers
+ },
+ findMarks: function(from, to, filter) {
+ from = clipPos(this, from); to = clipPos(this, to);
+ var found = [], lineNo$$1 = from.line;
+ this.iter(from.line, to.line + 1, function (line) {
+ var spans = line.markedSpans;
+ if (spans) { for (var i = 0; i < spans.length; i++) {
+ var span = spans[i];
+ if (!(span.to != null && lineNo$$1 == from.line && from.ch >= span.to ||
+ span.from == null && lineNo$$1 != from.line ||
+ span.from != null && lineNo$$1 == to.line && span.from >= to.ch) &&
+ (!filter || filter(span.marker)))
+ { found.push(span.marker.parent || span.marker); }
+ } }
+ ++lineNo$$1;
+ });
+ return found
+ },
+ getAllMarks: function() {
+ var markers = [];
+ this.iter(function (line) {
+ var sps = line.markedSpans;
+ if (sps) { for (var i = 0; i < sps.length; ++i)
+ { if (sps[i].from != null) { markers.push(sps[i].marker); } } }
+ });
+ return markers
+ },
+
+ posFromIndex: function(off) {
+ var ch, lineNo$$1 = this.first, sepSize = this.lineSeparator().length;
+ this.iter(function (line) {
+ var sz = line.text.length + sepSize;
+ if (sz > off) { ch = off; return true }
+ off -= sz;
+ ++lineNo$$1;
+ });
+ return clipPos(this, Pos(lineNo$$1, ch))
+ },
+ indexFromPos: function (coords) {
+ coords = clipPos(this, coords);
+ var index = coords.ch;
+ if (coords.line < this.first || coords.ch < 0) { return 0 }
+ var sepSize = this.lineSeparator().length;
+ this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value
+ index += line.text.length + sepSize;
+ });
+ return index
+ },
+
+ copy: function(copyHistory) {
+ var doc = new Doc(getLines(this, this.first, this.first + this.size),
+ this.modeOption, this.first, this.lineSep, this.direction);
+ doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
+ doc.sel = this.sel;
+ doc.extend = false;
+ if (copyHistory) {
+ doc.history.undoDepth = this.history.undoDepth;
+ doc.setHistory(this.getHistory());
+ }
+ return doc
+ },
+
+ linkedDoc: function(options) {
+ if (!options) { options = {}; }
+ var from = this.first, to = this.first + this.size;
+ if (options.from != null && options.from > from) { from = options.from; }
+ if (options.to != null && options.to < to) { to = options.to; }
+ var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction);
+ if (options.sharedHist) { copy.history = this.history
+ ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
+ copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
+ copySharedMarkers(copy, findSharedMarkers(this));
+ return copy
+ },
+ unlinkDoc: function(other) {
+ var this$1 = this;
+
+ if (other instanceof CodeMirror) { other = other.doc; }
+ if (this.linked) { for (var i = 0; i < this.linked.length; ++i) {
+ var link = this$1.linked[i];
+ if (link.doc != other) { continue }
+ this$1.linked.splice(i, 1);
+ other.unlinkDoc(this$1);
+ detachSharedMarkers(findSharedMarkers(this$1));
+ break
+ } }
+ // If the histories were shared, split them again
+ if (other.history == this.history) {
+ var splitIds = [other.id];
+ linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true);
+ other.history = new History(null);
+ other.history.done = copyHistoryArray(this.history.done, splitIds);
+ other.history.undone = copyHistoryArray(this.history.undone, splitIds);
+ }
+ },
+ iterLinkedDocs: function(f) {linkedDocs(this, f);},
+
+ getMode: function() {return this.mode},
+ getEditor: function() {return this.cm},
+
+ splitLines: function(str) {
+ if (this.lineSep) { return str.split(this.lineSep) }
+ return splitLinesAuto(str)
+ },
+ lineSeparator: function() { return this.lineSep || "\n" },
+
+ setDirection: docMethodOp(function (dir) {
+ if (dir != "rtl") { dir = "ltr"; }
+ if (dir == this.direction) { return }
+ this.direction = dir;
+ this.iter(function (line) { return line.order = null; });
+ if (this.cm) { directionChanged(this.cm); }
+ })
+ });
+
+ // Public alias.
+ Doc.prototype.eachLine = Doc.prototype.iter;
+
+ // Kludge to work around strange IE behavior where it'll sometimes
+ // re-fire a series of drag-related events right after the drop (#1551)
+ var lastDrop = 0;
+
+ function onDrop(e) {
+ var cm = this;
+ clearDragCursor(cm);
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
+ { return }
+ e_preventDefault(e);
+ if (ie) { lastDrop = +new Date; }
+ var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
+ if (!pos || cm.isReadOnly()) { return }
+ // Might be a file drop, in which case we simply extract the text
+ // and insert it.
+ if (files && files.length && window.FileReader && window.File) {
+ var n = files.length, text = Array(n), read = 0;
+ var loadFile = function (file, i) {
+ if (cm.options.allowDropFileTypes &&
+ indexOf(cm.options.allowDropFileTypes, file.type) == -1)
+ { return }
+
+ var reader = new FileReader;
+ reader.onload = operation(cm, function () {
+ var content = reader.result;
+ if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = ""; }
+ text[i] = content;
+ if (++read == n) {
+ pos = clipPos(cm.doc, pos);
+ var change = {from: pos, to: pos,
+ text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())),
+ origin: "paste"};
+ makeChange(cm.doc, change);
+ setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
+ }
+ });
+ reader.readAsText(file);
+ };
+ for (var i = 0; i < n; ++i) { loadFile(files[i], i); }
+ } else { // Normal drop
+ // Don't do a replace if the drop happened inside of the selected text.
+ if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
+ cm.state.draggingText(e);
+ // Ensure the editor is re-focused
+ setTimeout(function () { return cm.display.input.focus(); }, 20);
+ return
+ }
+ try {
+ var text$1 = e.dataTransfer.getData("Text");
+ if (text$1) {
+ var selected;
+ if (cm.state.draggingText && !cm.state.draggingText.copy)
+ { selected = cm.listSelections(); }
+ setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
+ if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1)
+ { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag"); } }
+ cm.replaceSelection(text$1, "around", "paste");
+ cm.display.input.focus();
+ }
+ }
+ catch(e){}
+ }
+ }
+
+ function onDragStart(cm, e) {
+ if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return }
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return }
+
+ e.dataTransfer.setData("Text", cm.getSelection());
+ e.dataTransfer.effectAllowed = "copyMove";
+
+ // Use dummy image instead of default browsers image.
+ // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
+ if (e.dataTransfer.setDragImage && !safari) {
+ var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
+ img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
+ if (presto) {
+ img.width = img.height = 1;
+ cm.display.wrapper.appendChild(img);
+ // Force a relayout, or Opera won't use our image for some obscure reason
+ img._top = img.offsetTop;
+ }
+ e.dataTransfer.setDragImage(img, 0, 0);
+ if (presto) { img.parentNode.removeChild(img); }
+ }
+ }
+
+ function onDragOver(cm, e) {
+ var pos = posFromMouse(cm, e);
+ if (!pos) { return }
+ var frag = document.createDocumentFragment();
+ drawSelectionCursor(cm, pos, frag);
+ if (!cm.display.dragCursor) {
+ cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors");
+ cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv);
+ }
+ removeChildrenAndAdd(cm.display.dragCursor, frag);
+ }
+
+ function clearDragCursor(cm) {
+ if (cm.display.dragCursor) {
+ cm.display.lineSpace.removeChild(cm.display.dragCursor);
+ cm.display.dragCursor = null;
+ }
+ }
+
+ // These must be handled carefully, because naively registering a
+ // handler for each editor will cause the editors to never be
+ // garbage collected.
+
+ function forEachCodeMirror(f) {
+ if (!document.getElementsByClassName) { return }
+ var byClass = document.getElementsByClassName("CodeMirror"), editors = [];
+ for (var i = 0; i < byClass.length; i++) {
+ var cm = byClass[i].CodeMirror;
+ if (cm) { editors.push(cm); }
+ }
+ if (editors.length) { editors[0].operation(function () {
+ for (var i = 0; i < editors.length; i++) { f(editors[i]); }
+ }); }
+ }
+
+ var globalsRegistered = false;
+ function ensureGlobalHandlers() {
+ if (globalsRegistered) { return }
+ registerGlobalHandlers();
+ globalsRegistered = true;
+ }
+ function registerGlobalHandlers() {
+ // When the window resizes, we need to refresh active editors.
+ var resizeTimer;
+ on(window, "resize", function () {
+ if (resizeTimer == null) { resizeTimer = setTimeout(function () {
+ resizeTimer = null;
+ forEachCodeMirror(onResize);
+ }, 100); }
+ });
+ // When the window loses focus, we want to show the editor as blurred
+ on(window, "blur", function () { return forEachCodeMirror(onBlur); });
+ }
+ // Called when the window resizes
+ function onResize(cm) {
+ var d = cm.display;
+ // Might be a text scaling operation, clear size caches.
+ d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
+ d.scrollbarsClipped = false;
+ cm.setSize();
+ }
+
+ var keyNames = {
+ 3: "Pause", 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: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod",
+ 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 145: "ScrollLock",
+ 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
+ 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
+ 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"
+ };
+
+ // Number keys
+ for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); }
+ // Alphabetic keys
+ for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); }
+ // Function keys
+ for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2; }
+
+ var keyMap = {};
+
+ keyMap.basic = {
+ "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
+ "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
+ "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
+ "Tab": "defaultTab", "Shift-Tab": "indentAuto",
+ "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
+ "Esc": "singleSelection"
+ };
+ // Note that the save and find-related commands aren't defined by
+ // default. User code or addons can define them. 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", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown",
+ "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
+ "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
+ "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
+ "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
+ "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
+ "fallthrough": "basic"
+ };
+ // Very basic readline/emacs-style bindings, which are standard on Mac.
+ keyMap.emacsy = {
+ "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
+ "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
+ "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
+ "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars",
+ "Ctrl-O": "openLine"
+ };
+ keyMap.macDefault = {
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
+ "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
+ "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore",
+ "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
+ "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
+ "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
+ "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
+ "fallthrough": ["basic", "emacsy"]
+ };
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
+
+ // KEYMAP DISPATCH
+
+ function normalizeKeyName(name) {
+ var parts = name.split(/-(?!$)/);
+ name = parts[parts.length - 1];
+ var alt, ctrl, shift, cmd;
+ for (var i = 0; i < parts.length - 1; i++) {
+ var mod = parts[i];
+ if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; }
+ else if (/^a(lt)?$/i.test(mod)) { alt = true; }
+ else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; }
+ else if (/^s(hift)?$/i.test(mod)) { shift = true; }
+ else { throw new Error("Unrecognized modifier name: " + mod) }
+ }
+ if (alt) { name = "Alt-" + name; }
+ if (ctrl) { name = "Ctrl-" + name; }
+ if (cmd) { name = "Cmd-" + name; }
+ if (shift) { name = "Shift-" + name; }
+ return name
+ }
+
+ // This is a kludge to keep keymaps mostly working as raw objects
+ // (backwards compatibility) while at the same time support features
+ // like normalization and multi-stroke key bindings. It compiles a
+ // new normalized keymap, and then updates the old object to reflect
+ // this.
+ function normalizeKeyMap(keymap) {
+ var copy = {};
+ for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) {
+ var value = keymap[keyname];
+ if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue }
+ if (value == "...") { delete keymap[keyname]; continue }
+
+ var keys = map(keyname.split(" "), normalizeKeyName);
+ for (var i = 0; i < keys.length; i++) {
+ var val = (void 0), name = (void 0);
+ if (i == keys.length - 1) {
+ name = keys.join(" ");
+ val = value;
+ } else {
+ name = keys.slice(0, i + 1).join(" ");
+ val = "...";
+ }
+ var prev = copy[name];
+ if (!prev) { copy[name] = val; }
+ else if (prev != val) { throw new Error("Inconsistent bindings for " + name) }
+ }
+ delete keymap[keyname];
+ } }
+ for (var prop in copy) { keymap[prop] = copy[prop]; }
+ return keymap
+ }
+
+ function lookupKey(key, map$$1, handle, context) {
+ map$$1 = getKeyMap(map$$1);
+ var found = map$$1.call ? map$$1.call(key, context) : map$$1[key];
+ if (found === false) { return "nothing" }
+ if (found === "...") { return "multi" }
+ if (found != null && handle(found)) { return "handled" }
+
+ if (map$$1.fallthrough) {
+ if (Object.prototype.toString.call(map$$1.fallthrough) != "[object Array]")
+ { return lookupKey(key, map$$1.fallthrough, handle, context) }
+ for (var i = 0; i < map$$1.fallthrough.length; i++) {
+ var result = lookupKey(key, map$$1.fallthrough[i], handle, context);
+ if (result) { return result }
+ }
+ }
+ }
+
+ // Modifier key presses don't count as 'real' key presses for the
+ // purpose of keymap fallthrough.
+ function isModifierKey(value) {
+ var name = typeof value == "string" ? value : keyNames[value.keyCode];
+ return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"
+ }
+
+ function addModifierNames(name, event, noShift) {
+ var base = name;
+ if (event.altKey && base != "Alt") { name = "Alt-" + name; }
+ if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name; }
+ if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name; }
+ if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name; }
+ return name
+ }
+
+ // Look up the name of a key as indicated by an event object.
+ function keyName(event, noShift) {
+ if (presto && event.keyCode == 34 && event["char"]) { return false }
+ var name = keyNames[event.keyCode];
+ if (name == null || event.altGraphKey) { return false }
+ // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause,
+ // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+)
+ if (event.keyCode == 3 && event.code) { name = event.code; }
+ return addModifierNames(name, event, noShift)
+ }
+
+ function getKeyMap(val) {
+ return typeof val == "string" ? keyMap[val] : val
+ }
+
+ // Helper for deleting text near the selection(s), used to implement
+ // backspace, delete, and similar functionality.
+ function deleteNearSelection(cm, compute) {
+ var ranges = cm.doc.sel.ranges, kill = [];
+ // Build up a set of ranges to kill first, merging overlapping
+ // ranges.
+ for (var i = 0; i < ranges.length; i++) {
+ var toKill = compute(ranges[i]);
+ while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {
+ var replaced = kill.pop();
+ if (cmp(replaced.from, toKill.from) < 0) {
+ toKill.from = replaced.from;
+ break
+ }
+ }
+ kill.push(toKill);
+ }
+ // Next, remove those actual ranges.
+ runInOp(cm, function () {
+ for (var i = kill.length - 1; i >= 0; i--)
+ { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); }
+ ensureCursorVisible(cm);
+ });
+ }
+
+ function moveCharLogically(line, ch, dir) {
+ var target = skipExtendingChars(line.text, ch + dir, dir);
+ return target < 0 || target > line.text.length ? null : target
+ }
+
+ function moveLogically(line, start, dir) {
+ var ch = moveCharLogically(line, start.ch, dir);
+ return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before")
+ }
+
+ function endOfLine(visually, cm, lineObj, lineNo, dir) {
+ if (visually) {
+ var order = getOrder(lineObj, cm.doc.direction);
+ if (order) {
+ var part = dir < 0 ? lst(order) : order[0];
+ var moveInStorageOrder = (dir < 0) == (part.level == 1);
+ var sticky = moveInStorageOrder ? "after" : "before";
+ var ch;
+ // With a wrapped rtl chunk (possibly spanning multiple bidi parts),
+ // it could be that the last bidi part is not on the last visual line,
+ // since visual lines contain content order-consecutive chunks.
+ // Thus, in rtl, we are looking for the first (content-order) character
+ // in the rtl chunk that is on the last line (that is, the same line
+ // as the last (content-order) character).
+ if (part.level > 0 || cm.doc.direction == "rtl") {
+ var prep = prepareMeasureForLine(cm, lineObj);
+ ch = dir < 0 ? lineObj.text.length - 1 : 0;
+ var targetTop = measureCharPrepared(cm, prep, ch).top;
+ ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch);
+ if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1); }
+ } else { ch = dir < 0 ? part.to : part.from; }
+ return new Pos(lineNo, ch, sticky)
+ }
+ }
+ return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after")
+ }
+
+ function moveVisually(cm, line, start, dir) {
+ var bidi = getOrder(line, cm.doc.direction);
+ if (!bidi) { return moveLogically(line, start, dir) }
+ if (start.ch >= line.text.length) {
+ start.ch = line.text.length;
+ start.sticky = "before";
+ } else if (start.ch <= 0) {
+ start.ch = 0;
+ start.sticky = "after";
+ }
+ var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos];
+ if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) {
+ // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines,
+ // nothing interesting happens.
+ return moveLogically(line, start, dir)
+ }
+
+ var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); };
+ var prep;
+ var getWrappedLineExtent = function (ch) {
+ if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} }
+ prep = prep || prepareMeasureForLine(cm, line);
+ return wrappedLineExtentChar(cm, line, prep, ch)
+ };
+ var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch);
+
+ if (cm.doc.direction == "rtl" || part.level == 1) {
+ var moveInStorageOrder = (part.level == 1) == (dir < 0);
+ var ch = mv(start, moveInStorageOrder ? 1 : -1);
+ if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) {
+ // Case 2: We move within an rtl part or in an rtl editor on the same visual line
+ var sticky = moveInStorageOrder ? "before" : "after";
+ return new Pos(start.line, ch, sticky)
+ }
+ }
+
+ // Case 3: Could not move within this bidi part in this visual line, so leave
+ // the current bidi part
+
+ var searchInVisualLine = function (partPos, dir, wrappedLineExtent) {
+ var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder
+ ? new Pos(start.line, mv(ch, 1), "before")
+ : new Pos(start.line, ch, "after"); };
+
+ for (; partPos >= 0 && partPos < bidi.length; partPos += dir) {
+ var part = bidi[partPos];
+ var moveInStorageOrder = (dir > 0) == (part.level != 1);
+ var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1);
+ if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) }
+ ch = moveInStorageOrder ? part.from : mv(part.to, -1);
+ if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) }
+ }
+ };
+
+ // Case 3a: Look for other bidi parts on the same visual line
+ var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent);
+ if (res) { return res }
+
+ // Case 3b: Look for other bidi parts on the next visual line
+ var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1);
+ if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) {
+ res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh));
+ if (res) { return res }
+ }
+
+ // Case 4: Nowhere to move
+ return null
+ }
+
+ // Commands are parameter-less actions that can be performed on an
+ // editor, mostly used for keybindings.
+ var commands = {
+ selectAll: selectAll,
+ singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); },
+ killLine: function (cm) { return deleteNearSelection(cm, function (range) {
+ if (range.empty()) {
+ var len = getLine(cm.doc, range.head.line).text.length;
+ if (range.head.ch == len && range.head.line < cm.lastLine())
+ { return {from: range.head, to: Pos(range.head.line + 1, 0)} }
+ else
+ { return {from: range.head, to: Pos(range.head.line, len)} }
+ } else {
+ return {from: range.from(), to: range.to()}
+ }
+ }); },
+ deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({
+ from: Pos(range.from().line, 0),
+ to: clipPos(cm.doc, Pos(range.to().line + 1, 0))
+ }); }); },
+ delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({
+ from: Pos(range.from().line, 0), to: range.from()
+ }); }); },
+ delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) {
+ var top = cm.charCoords(range.head, "div").top + 5;
+ var leftPos = cm.coordsChar({left: 0, top: top}, "div");
+ return {from: leftPos, to: range.from()}
+ }); },
+ delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) {
+ var top = cm.charCoords(range.head, "div").top + 5;
+ var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
+ return {from: range.from(), to: rightPos }
+ }); },
+ undo: function (cm) { return cm.undo(); },
+ redo: function (cm) { return cm.redo(); },
+ undoSelection: function (cm) { return cm.undoSelection(); },
+ redoSelection: function (cm) { return cm.redoSelection(); },
+ goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); },
+ goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); },
+ goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); },
+ {origin: "+move", bias: 1}
+ ); },
+ goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); },
+ {origin: "+move", bias: 1}
+ ); },
+ goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); },
+ {origin: "+move", bias: -1}
+ ); },
+ goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) {
+ var top = cm.cursorCoords(range.head, "div").top + 5;
+ return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
+ }, sel_move); },
+ goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) {
+ var top = cm.cursorCoords(range.head, "div").top + 5;
+ return cm.coordsChar({left: 0, top: top}, "div")
+ }, sel_move); },
+ goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) {
+ var top = cm.cursorCoords(range.head, "div").top + 5;
+ var pos = cm.coordsChar({left: 0, top: top}, "div");
+ if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) }
+ return pos
+ }, sel_move); },
+ goLineUp: function (cm) { return cm.moveV(-1, "line"); },
+ goLineDown: function (cm) { return cm.moveV(1, "line"); },
+ goPageUp: function (cm) { return cm.moveV(-1, "page"); },
+ goPageDown: function (cm) { return cm.moveV(1, "page"); },
+ goCharLeft: function (cm) { return cm.moveH(-1, "char"); },
+ goCharRight: function (cm) { return cm.moveH(1, "char"); },
+ goColumnLeft: function (cm) { return cm.moveH(-1, "column"); },
+ goColumnRight: function (cm) { return cm.moveH(1, "column"); },
+ goWordLeft: function (cm) { return cm.moveH(-1, "word"); },
+ goGroupRight: function (cm) { return cm.moveH(1, "group"); },
+ goGroupLeft: function (cm) { return cm.moveH(-1, "group"); },
+ goWordRight: function (cm) { return cm.moveH(1, "word"); },
+ delCharBefore: function (cm) { return cm.deleteH(-1, "char"); },
+ delCharAfter: function (cm) { return cm.deleteH(1, "char"); },
+ delWordBefore: function (cm) { return cm.deleteH(-1, "word"); },
+ delWordAfter: function (cm) { return cm.deleteH(1, "word"); },
+ delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); },
+ delGroupAfter: function (cm) { return cm.deleteH(1, "group"); },
+ indentAuto: function (cm) { return cm.indentSelection("smart"); },
+ indentMore: function (cm) { return cm.indentSelection("add"); },
+ indentLess: function (cm) { return cm.indentSelection("subtract"); },
+ insertTab: function (cm) { return cm.replaceSelection("\t"); },
+ insertSoftTab: function (cm) {
+ var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize;
+ for (var i = 0; i < ranges.length; i++) {
+ var pos = ranges[i].from();
+ var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);
+ spaces.push(spaceStr(tabSize - col % tabSize));
+ }
+ cm.replaceSelections(spaces);
+ },
+ defaultTab: function (cm) {
+ if (cm.somethingSelected()) { cm.indentSelection("add"); }
+ else { cm.execCommand("insertTab"); }
+ },
+ // Swap the two chars left and right of each selection's head.
+ // Move cursor behind the two swapped characters afterwards.
+ //
+ // Doesn't consider line feeds a character.
+ // Doesn't scan more than one line above to find a character.
+ // Doesn't do anything on an empty line.
+ // Doesn't do anything with non-empty selections.
+ transposeChars: function (cm) { return runInOp(cm, function () {
+ var ranges = cm.listSelections(), newSel = [];
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) { continue }
+ var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;
+ if (line) {
+ if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); }
+ if (cur.ch > 0) {
+ cur = new Pos(cur.line, cur.ch + 1);
+ cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
+ Pos(cur.line, cur.ch - 2), cur, "+transpose");
+ } else if (cur.line > cm.doc.first) {
+ var prev = getLine(cm.doc, cur.line - 1).text;
+ if (prev) {
+ cur = new Pos(cur.line, 1);
+ cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
+ prev.charAt(prev.length - 1),
+ Pos(cur.line - 1, prev.length - 1), cur, "+transpose");
+ }
+ }
+ }
+ newSel.push(new Range(cur, cur));
+ }
+ cm.setSelections(newSel);
+ }); },
+ newlineAndIndent: function (cm) { return runInOp(cm, function () {
+ var sels = cm.listSelections();
+ for (var i = sels.length - 1; i >= 0; i--)
+ { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input"); }
+ sels = cm.listSelections();
+ for (var i$1 = 0; i$1 < sels.length; i$1++)
+ { cm.indentLine(sels[i$1].from().line, null, true); }
+ ensureCursorVisible(cm);
+ }); },
+ openLine: function (cm) { return cm.replaceSelection("\n", "start"); },
+ toggleOverwrite: function (cm) { return cm.toggleOverwrite(); }
+ };
+
+
+ function lineStart(cm, lineN) {
+ var line = getLine(cm.doc, lineN);
+ var visual = visualLine(line);
+ if (visual != line) { lineN = lineNo(visual); }
+ return endOfLine(true, cm, visual, lineN, 1)
+ }
+ function lineEnd(cm, lineN) {
+ var line = getLine(cm.doc, lineN);
+ var visual = visualLineEnd(line);
+ if (visual != line) { lineN = lineNo(visual); }
+ return endOfLine(true, cm, line, lineN, -1)
+ }
+ function lineStartSmart(cm, pos) {
+ var start = lineStart(cm, pos.line);
+ var line = getLine(cm.doc, start.line);
+ var order = getOrder(line, cm.doc.direction);
+ if (!order || order[0].level == 0) {
+ var firstNonWS = Math.max(0, line.text.search(/\S/));
+ var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;
+ return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky)
+ }
+ return start
+ }
+
+ // Run a handler that was bound to a key.
+ function doHandleBinding(cm, bound, dropShift) {
+ if (typeof bound == "string") {
+ bound = commands[bound];
+ if (!bound) { return false }
+ }
+ // Ensure previous input has been read, so that the handler sees a
+ // consistent view of the document
+ cm.display.input.ensurePolled();
+ var prevShift = cm.display.shift, done = false;
+ try {
+ if (cm.isReadOnly()) { cm.state.suppressEdits = true; }
+ if (dropShift) { cm.display.shift = false; }
+ done = bound(cm) != Pass;
+ } finally {
+ cm.display.shift = prevShift;
+ cm.state.suppressEdits = false;
+ }
+ return done
+ }
+
+ function lookupKeyForEditor(cm, name, handle) {
+ for (var i = 0; i < cm.state.keyMaps.length; i++) {
+ var result = lookupKey(name, cm.state.keyMaps[i], handle, cm);
+ if (result) { return result }
+ }
+ return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm))
+ || lookupKey(name, cm.options.keyMap, handle, cm)
+ }
+
+ // Note that, despite the name, this function is also used to check
+ // for bound mouse clicks.
+
+ var stopSeq = new Delayed;
+
+ function dispatchKey(cm, name, e, handle) {
+ var seq = cm.state.keySeq;
+ if (seq) {
+ if (isModifierKey(name)) { return "handled" }
+ if (/\'$/.test(name))
+ { cm.state.keySeq = null; }
+ else
+ { stopSeq.set(50, function () {
+ if (cm.state.keySeq == seq) {
+ cm.state.keySeq = null;
+ cm.display.input.reset();
+ }
+ }); }
+ if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true }
+ }
+ return dispatchKeyInner(cm, name, e, handle)
+ }
+
+ function dispatchKeyInner(cm, name, e, handle) {
+ var result = lookupKeyForEditor(cm, name, handle);
+
+ if (result == "multi")
+ { cm.state.keySeq = name; }
+ if (result == "handled")
+ { signalLater(cm, "keyHandled", cm, name, e); }
+
+ if (result == "handled" || result == "multi") {
+ e_preventDefault(e);
+ restartBlink(cm);
+ }
+
+ return !!result
+ }
+
+ // Handle a key from the keydown event.
+ function handleKeyBinding(cm, e) {
+ var name = keyName(e, true);
+ if (!name) { return false }
+
+ if (e.shiftKey && !cm.state.keySeq) {
+ // First try to resolve full name (including 'Shift-'). Failing
+ // that, see if there is a cursor-motion command (starting with
+ // 'go') bound to the keyname without 'Shift-'.
+ return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); })
+ || dispatchKey(cm, name, e, function (b) {
+ if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
+ { return doHandleBinding(cm, b) }
+ })
+ } else {
+ return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); })
+ }
+ }
+
+ // Handle a key from the keypress event
+ function handleCharBinding(cm, e, ch) {
+ return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); })
+ }
+
+ var lastStoppedKey = null;
+ function onKeyDown(e) {
+ var cm = this;
+ cm.curOp.focus = activeElt();
+ if (signalDOMEvent(cm, e)) { return }
+ // IE does strange things with escape.
+ if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; }
+ var code = e.keyCode;
+ cm.display.shift = code == 16 || e.shiftKey;
+ var handled = handleKeyBinding(cm, e);
+ if (presto) {
+ lastStoppedKey = handled ? code : null;
+ // Opera has no cut event... we try to at least catch the key combo
+ if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
+ { cm.replaceSelection("", null, "cut"); }
+ }
+
+ // Turn mouse into crosshair when Alt is held on Mac.
+ if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
+ { showCrossHair(cm); }
+ }
+
+ function showCrossHair(cm) {
+ var lineDiv = cm.display.lineDiv;
+ addClass(lineDiv, "CodeMirror-crosshair");
+
+ function up(e) {
+ if (e.keyCode == 18 || !e.altKey) {
+ rmClass(lineDiv, "CodeMirror-crosshair");
+ off(document, "keyup", up);
+ off(document, "mouseover", up);
+ }
+ }
+ on(document, "keyup", up);
+ on(document, "mouseover", up);
+ }
+
+ function onKeyUp(e) {
+ if (e.keyCode == 16) { this.doc.sel.shift = false; }
+ signalDOMEvent(this, e);
+ }
+
+ function onKeyPress(e) {
+ var cm = this;
+ if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return }
+ var keyCode = e.keyCode, charCode = e.charCode;
+ if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return}
+ if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return }
+ var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
+ // Some browsers fire keypress events for backspace
+ if (ch == "\x08") { return }
+ if (handleCharBinding(cm, e, ch)) { return }
+ cm.display.input.onKeyPress(e);
+ }
+
+ var DOUBLECLICK_DELAY = 400;
+
+ var PastClick = function(time, pos, button) {
+ this.time = time;
+ this.pos = pos;
+ this.button = button;
+ };
+
+ PastClick.prototype.compare = function (time, pos, button) {
+ return this.time + DOUBLECLICK_DELAY > time &&
+ cmp(pos, this.pos) == 0 && button == this.button
+ };
+
+ var lastClick, lastDoubleClick;
+ function clickRepeat(pos, button) {
+ var now = +new Date;
+ if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) {
+ lastClick = lastDoubleClick = null;
+ return "triple"
+ } else if (lastClick && lastClick.compare(now, pos, button)) {
+ lastDoubleClick = new PastClick(now, pos, button);
+ lastClick = null;
+ return "double"
+ } else {
+ lastClick = new PastClick(now, pos, button);
+ lastDoubleClick = null;
+ return "single"
+ }
+ }
+
+ // A mouse down can be a single click, double click, triple click,
+ // start of selection drag, start of text drag, new cursor
+ // (ctrl-click), rectangle drag (alt-drag), or xwin
+ // middle-click-paste. Or it might be a click on something we should
+ // not interfere with, such as a scrollbar or widget.
+ function onMouseDown(e) {
+ var cm = this, display = cm.display;
+ if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return }
+ display.input.ensurePolled();
+ display.shift = e.shiftKey;
+
+ if (eventInWidget(display, e)) {
+ if (!webkit) {
+ // Briefly turn off draggability, to allow widgets to do
+ // normal dragging things.
+ display.scroller.draggable = false;
+ setTimeout(function () { return display.scroller.draggable = true; }, 100);
+ }
+ return
+ }
+ if (clickInGutter(cm, e)) { return }
+ var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single";
+ window.focus();
+
+ // #3261: make sure, that we're not starting a second selection
+ if (button == 1 && cm.state.selectingText)
+ { cm.state.selectingText(e); }
+
+ if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return }
+
+ if (button == 1) {
+ if (pos) { leftButtonDown(cm, pos, repeat, e); }
+ else if (e_target(e) == display.scroller) { e_preventDefault(e); }
+ } else if (button == 2) {
+ if (pos) { extendSelection(cm.doc, pos); }
+ setTimeout(function () { return display.input.focus(); }, 20);
+ } else if (button == 3) {
+ if (captureRightClick) { cm.display.input.onContextMenu(e); }
+ else { delayBlurEvent(cm); }
+ }
+ }
+
+ function handleMappedButton(cm, button, pos, repeat, event) {
+ var name = "Click";
+ if (repeat == "double") { name = "Double" + name; }
+ else if (repeat == "triple") { name = "Triple" + name; }
+ name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name;
+
+ return dispatchKey(cm, addModifierNames(name, event), event, function (bound) {
+ if (typeof bound == "string") { bound = commands[bound]; }
+ if (!bound) { return false }
+ var done = false;
+ try {
+ if (cm.isReadOnly()) { cm.state.suppressEdits = true; }
+ done = bound(cm, pos) != Pass;
+ } finally {
+ cm.state.suppressEdits = false;
+ }
+ return done
+ })
+ }
+
+ function configureMouse(cm, repeat, event) {
+ var option = cm.getOption("configureMouse");
+ var value = option ? option(cm, repeat, event) : {};
+ if (value.unit == null) {
+ var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey;
+ value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line";
+ }
+ if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; }
+ if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; }
+ if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); }
+ return value
+ }
+
+ function leftButtonDown(cm, pos, repeat, event) {
+ if (ie) { setTimeout(bind(ensureFocus, cm), 0); }
+ else { cm.curOp.focus = activeElt(); }
+
+ var behavior = configureMouse(cm, repeat, event);
+
+ var sel = cm.doc.sel, contained;
+ if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
+ repeat == "single" && (contained = sel.contains(pos)) > -1 &&
+ (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) &&
+ (cmp(contained.to(), pos) > 0 || pos.xRel < 0))
+ { leftButtonStartDrag(cm, event, pos, behavior); }
+ else
+ { leftButtonSelect(cm, event, pos, behavior); }
+ }
+
+ // Start a text drag. When it ends, see if any dragging actually
+ // happen, and treat as a click if it didn't.
+ function leftButtonStartDrag(cm, event, pos, behavior) {
+ var display = cm.display, moved = false;
+ var dragEnd = operation(cm, function (e) {
+ if (webkit) { display.scroller.draggable = false; }
+ cm.state.draggingText = false;
+ off(display.wrapper.ownerDocument, "mouseup", dragEnd);
+ off(display.wrapper.ownerDocument, "mousemove", mouseMove);
+ off(display.scroller, "dragstart", dragStart);
+ off(display.scroller, "drop", dragEnd);
+ if (!moved) {
+ e_preventDefault(e);
+ if (!behavior.addNew)
+ { extendSelection(cm.doc, pos, null, null, behavior.extend); }
+ // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)
+ if (webkit || ie && ie_version == 9)
+ { setTimeout(function () {display.wrapper.ownerDocument.body.focus(); display.input.focus();}, 20); }
+ else
+ { display.input.focus(); }
+ }
+ });
+ var mouseMove = function(e2) {
+ moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10;
+ };
+ var dragStart = function () { return moved = true; };
+ // Let the drag handler handle this.
+ if (webkit) { display.scroller.draggable = true; }
+ cm.state.draggingText = dragEnd;
+ dragEnd.copy = !behavior.moveOnDrag;
+ // IE's approach to draggable
+ if (display.scroller.dragDrop) { display.scroller.dragDrop(); }
+ on(display.wrapper.ownerDocument, "mouseup", dragEnd);
+ on(display.wrapper.ownerDocument, "mousemove", mouseMove);
+ on(display.scroller, "dragstart", dragStart);
+ on(display.scroller, "drop", dragEnd);
+
+ delayBlurEvent(cm);
+ setTimeout(function () { return display.input.focus(); }, 20);
+ }
+
+ function rangeForUnit(cm, pos, unit) {
+ if (unit == "char") { return new Range(pos, pos) }
+ if (unit == "word") { return cm.findWordAt(pos) }
+ if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) }
+ var result = unit(cm, pos);
+ return new Range(result.from, result.to)
+ }
+
+ // Normal selection, as opposed to text dragging.
+ function leftButtonSelect(cm, event, start, behavior) {
+ var display = cm.display, doc = cm.doc;
+ e_preventDefault(event);
+
+ var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;
+ if (behavior.addNew && !behavior.extend) {
+ ourIndex = doc.sel.contains(start);
+ if (ourIndex > -1)
+ { ourRange = ranges[ourIndex]; }
+ else
+ { ourRange = new Range(start, start); }
+ } else {
+ ourRange = doc.sel.primary();
+ ourIndex = doc.sel.primIndex;
+ }
+
+ if (behavior.unit == "rectangle") {
+ if (!behavior.addNew) { ourRange = new Range(start, start); }
+ start = posFromMouse(cm, event, true, true);
+ ourIndex = -1;
+ } else {
+ var range$$1 = rangeForUnit(cm, start, behavior.unit);
+ if (behavior.extend)
+ { ourRange = extendRange(ourRange, range$$1.anchor, range$$1.head, behavior.extend); }
+ else
+ { ourRange = range$$1; }
+ }
+
+ if (!behavior.addNew) {
+ ourIndex = 0;
+ setSelection(doc, new Selection([ourRange], 0), sel_mouse);
+ startSel = doc.sel;
+ } else if (ourIndex == -1) {
+ ourIndex = ranges.length;
+ setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex),
+ {scroll: false, origin: "*mouse"});
+ } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) {
+ setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0),
+ {scroll: false, origin: "*mouse"});
+ startSel = doc.sel;
+ } else {
+ replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
+ }
+
+ var lastPos = start;
+ function extendTo(pos) {
+ if (cmp(lastPos, pos) == 0) { return }
+ lastPos = pos;
+
+ if (behavior.unit == "rectangle") {
+ var ranges = [], tabSize = cm.options.tabSize;
+ var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);
+ var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);
+ var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);
+ for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));
+ line <= end; line++) {
+ var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);
+ if (left == right)
+ { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); }
+ else if (text.length > leftPos)
+ { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); }
+ }
+ if (!ranges.length) { ranges.push(new Range(start, start)); }
+ setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),
+ {origin: "*mouse", scroll: false});
+ cm.scrollIntoView(pos);
+ } else {
+ var oldRange = ourRange;
+ var range$$1 = rangeForUnit(cm, pos, behavior.unit);
+ var anchor = oldRange.anchor, head;
+ if (cmp(range$$1.anchor, anchor) > 0) {
+ head = range$$1.head;
+ anchor = minPos(oldRange.from(), range$$1.anchor);
+ } else {
+ head = range$$1.anchor;
+ anchor = maxPos(oldRange.to(), range$$1.head);
+ }
+ var ranges$1 = startSel.ranges.slice(0);
+ ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head));
+ setSelection(doc, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse);
+ }
+ }
+
+ var editorSize = display.wrapper.getBoundingClientRect();
+ // Used to ensure timeout re-tries don't fire when another extend
+ // happened in the meantime (clearTimeout isn't reliable -- at
+ // least on Chrome, the timeouts still happen even when cleared,
+ // if the clear happens after their scheduled firing time).
+ var counter = 0;
+
+ function extend(e) {
+ var curCount = ++counter;
+ var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle");
+ if (!cur) { return }
+ if (cmp(cur, lastPos) != 0) {
+ cm.curOp.focus = activeElt();
+ extendTo(cur);
+ var visible = visibleLines(display, doc);
+ if (cur.line >= visible.to || cur.line < visible.from)
+ { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e); }}), 150); }
+ } else {
+ var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
+ if (outside) { setTimeout(operation(cm, function () {
+ if (counter != curCount) { return }
+ display.scroller.scrollTop += outside;
+ extend(e);
+ }), 50); }
+ }
+ }
+
+ function done(e) {
+ cm.state.selectingText = false;
+ counter = Infinity;
+ // If e is null or undefined we interpret this as someone trying
+ // to explicitly cancel the selection rather than the user
+ // letting go of the mouse button.
+ if (e) {
+ e_preventDefault(e);
+ display.input.focus();
+ }
+ off(display.wrapper.ownerDocument, "mousemove", move);
+ off(display.wrapper.ownerDocument, "mouseup", up);
+ doc.history.lastSelOrigin = null;
+ }
+
+ var move = operation(cm, function (e) {
+ if (e.buttons === 0 || !e_button(e)) { done(e); }
+ else { extend(e); }
+ });
+ var up = operation(cm, done);
+ cm.state.selectingText = up;
+ on(display.wrapper.ownerDocument, "mousemove", move);
+ on(display.wrapper.ownerDocument, "mouseup", up);
+ }
+
+ // Used when mouse-selecting to adjust the anchor to the proper side
+ // of a bidi jump depending on the visual position of the head.
+ function bidiSimplify(cm, range$$1) {
+ var anchor = range$$1.anchor;
+ var head = range$$1.head;
+ var anchorLine = getLine(cm.doc, anchor.line);
+ if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range$$1 }
+ var order = getOrder(anchorLine);
+ if (!order) { return range$$1 }
+ var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index];
+ if (part.from != anchor.ch && part.to != anchor.ch) { return range$$1 }
+ var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1);
+ if (boundary == 0 || boundary == order.length) { return range$$1 }
+
+ // Compute the relative visual position of the head compared to the
+ // anchor (<0 is to the left, >0 to the right)
+ var leftSide;
+ if (head.line != anchor.line) {
+ leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0;
+ } else {
+ var headIndex = getBidiPartAt(order, head.ch, head.sticky);
+ var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1);
+ if (headIndex == boundary - 1 || headIndex == boundary)
+ { leftSide = dir < 0; }
+ else
+ { leftSide = dir > 0; }
+ }
+
+ var usePart = order[boundary + (leftSide ? -1 : 0)];
+ var from = leftSide == (usePart.level == 1);
+ var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before";
+ return anchor.ch == ch && anchor.sticky == sticky ? range$$1 : new Range(new Pos(anchor.line, ch, sticky), head)
+ }
+
+
+ // Determines whether an event happened in the gutter, and fires the
+ // handlers for the corresponding event.
+ function gutterEvent(cm, e, type, prevent) {
+ var mX, mY;
+ if (e.touches) {
+ mX = e.touches[0].clientX;
+ mY = e.touches[0].clientY;
+ } else {
+ try { mX = e.clientX; mY = e.clientY; }
+ catch(e) { return false }
+ }
+ if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false }
+ if (prevent) { e_preventDefault(e); }
+
+ var display = cm.display;
+ var lineBox = display.lineDiv.getBoundingClientRect();
+
+ if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) }
+ mY -= lineBox.top - display.viewOffset;
+
+ for (var i = 0; i < cm.display.gutterSpecs.length; ++i) {
+ var g = display.gutters.childNodes[i];
+ if (g && g.getBoundingClientRect().right >= mX) {
+ var line = lineAtHeight(cm.doc, mY);
+ var gutter = cm.display.gutterSpecs[i];
+ signal(cm, type, cm, line, gutter.className, e);
+ return e_defaultPrevented(e)
+ }
+ }
+ }
+
+ function clickInGutter(cm, e) {
+ return gutterEvent(cm, e, "gutterClick", true)
+ }
+
+ // CONTEXT MENU HANDLING
+
+ // To make the context menu work, we need to briefly unhide the
+ // textarea (making it as unobtrusive as possible) to let the
+ // right-click take effect on it.
+ function onContextMenu(cm, e) {
+ if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return }
+ if (signalDOMEvent(cm, e, "contextmenu")) { return }
+ if (!captureRightClick) { cm.display.input.onContextMenu(e); }
+ }
+
+ function contextMenuInGutter(cm, e) {
+ if (!hasHandler(cm, "gutterContextMenu")) { return false }
+ return gutterEvent(cm, e, "gutterContextMenu", false)
+ }
+
+ function themeChanged(cm) {
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
+ cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
+ clearCaches(cm);
+ }
+
+ var Init = {toString: function(){return "CodeMirror.Init"}};
+
+ var defaults = {};
+ var optionHandlers = {};
+
+ function defineOptions(CodeMirror) {
+ var optionHandlers = CodeMirror.optionHandlers;
+
+ function option(name, deflt, handle, notOnInit) {
+ CodeMirror.defaults[name] = deflt;
+ if (handle) { optionHandlers[name] =
+ notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old); }} : handle; }
+ }
+
+ CodeMirror.defineOption = option;
+
+ // Passed to option handlers when there is no old value.
+ CodeMirror.Init = Init;
+
+ // These two are, on init, called from the constructor because they
+ // have to be initialized before the editor can start at all.
+ option("value", "", function (cm, val) { return cm.setValue(val); }, true);
+ option("mode", null, function (cm, val) {
+ cm.doc.modeOption = val;
+ loadMode(cm);
+ }, true);
+
+ option("indentUnit", 2, loadMode, true);
+ option("indentWithTabs", false);
+ option("smartIndent", true);
+ option("tabSize", 4, function (cm) {
+ resetModeState(cm);
+ clearCaches(cm);
+ regChange(cm);
+ }, true);
+
+ option("lineSeparator", null, function (cm, val) {
+ cm.doc.lineSep = val;
+ if (!val) { return }
+ var newBreaks = [], lineNo = cm.doc.first;
+ cm.doc.iter(function (line) {
+ for (var pos = 0;;) {
+ var found = line.text.indexOf(val, pos);
+ if (found == -1) { break }
+ pos = found + val.length;
+ newBreaks.push(Pos(lineNo, found));
+ }
+ lineNo++;
+ });
+ for (var i = newBreaks.length - 1; i >= 0; i--)
+ { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); }
+ });
+ option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, function (cm, val, old) {
+ cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
+ if (old != Init) { cm.refresh(); }
+ });
+ option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true);
+ option("electricChars", true);
+ option("inputStyle", mobile ? "contenteditable" : "textarea", function () {
+ throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME
+ }, true);
+ option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true);
+ option("autocorrect", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true);
+ option("autocapitalize", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true);
+ option("rtlMoveVisually", !windows);
+ option("wholeLineUpdateBefore", true);
+
+ option("theme", "default", function (cm) {
+ themeChanged(cm);
+ updateGutters(cm);
+ }, true);
+ option("keyMap", "default", function (cm, val, old) {
+ var next = getKeyMap(val);
+ var prev = old != Init && getKeyMap(old);
+ if (prev && prev.detach) { prev.detach(cm, next); }
+ if (next.attach) { next.attach(cm, prev || null); }
+ });
+ option("extraKeys", null);
+ option("configureMouse", null);
+
+ option("lineWrapping", false, wrappingChanged, true);
+ option("gutters", [], function (cm, val) {
+ cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers);
+ updateGutters(cm);
+ }, true);
+ option("fixedGutter", true, function (cm, val) {
+ cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
+ cm.refresh();
+ }, true);
+ option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true);
+ option("scrollbarStyle", "native", function (cm) {
+ initScrollbars(cm);
+ updateScrollbars(cm);
+ cm.display.scrollbars.setScrollTop(cm.doc.scrollTop);
+ cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft);
+ }, true);
+ option("lineNumbers", false, function (cm, val) {
+ cm.display.gutterSpecs = getGutters(cm.options.gutters, val);
+ updateGutters(cm);
+ }, true);
+ option("firstLineNumber", 1, updateGutters, true);
+ option("lineNumberFormatter", function (integer) { return integer; }, updateGutters, true);
+ option("showCursorWhenSelecting", false, updateSelection, true);
+
+ option("resetSelectionOnContextMenu", true);
+ option("lineWiseCopyCut", true);
+ option("pasteLinesPerSelection", true);
+ option("selectionsMayTouch", false);
+
+ option("readOnly", false, function (cm, val) {
+ if (val == "nocursor") {
+ onBlur(cm);
+ cm.display.input.blur();
+ }
+ cm.display.input.readOnlyChanged(val);
+ });
+ option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true);
+ option("dragDrop", true, dragDropChanged);
+ option("allowDropFileTypes", null);
+
+ option("cursorBlinkRate", 530);
+ option("cursorScrollMargin", 0);
+ option("cursorHeight", 1, updateSelection, true);
+ option("singleCursorHeightPerLine", true, updateSelection, true);
+ option("workTime", 100);
+ option("workDelay", 100);
+ option("flattenSpans", true, resetModeState, true);
+ option("addModeClass", false, resetModeState, true);
+ option("pollInterval", 100);
+ option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; });
+ option("historyEventDelay", 1250);
+ option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true);
+ option("maxHighlightLength", 10000, resetModeState, true);
+ option("moveInputWithCursor", true, function (cm, val) {
+ if (!val) { cm.display.input.resetPosition(); }
+ });
+
+ option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; });
+ option("autofocus", null);
+ option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true);
+ option("phrases", null);
+ }
+
+ function dragDropChanged(cm, value, old) {
+ var wasOn = old && old != Init;
+ if (!value != !wasOn) {
+ var funcs = cm.display.dragFunctions;
+ var toggle = value ? on : off;
+ toggle(cm.display.scroller, "dragstart", funcs.start);
+ toggle(cm.display.scroller, "dragenter", funcs.enter);
+ toggle(cm.display.scroller, "dragover", funcs.over);
+ toggle(cm.display.scroller, "dragleave", funcs.leave);
+ toggle(cm.display.scroller, "drop", funcs.drop);
+ }
+ }
+
+ function wrappingChanged(cm) {
+ if (cm.options.lineWrapping) {
+ addClass(cm.display.wrapper, "CodeMirror-wrap");
+ cm.display.sizer.style.minWidth = "";
+ cm.display.sizerWidth = null;
+ } else {
+ rmClass(cm.display.wrapper, "CodeMirror-wrap");
+ findMaxLine(cm);
+ }
+ estimateLineHeights(cm);
+ regChange(cm);
+ clearCaches(cm);
+ setTimeout(function () { return updateScrollbars(cm); }, 100);
+ }
+
+ // A CodeMirror instance represents an editor. This is the object
+ // that user code is usually dealing with.
+
+ function CodeMirror(place, options) {
+ var this$1 = this;
+
+ if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) }
+
+ this.options = options = options ? copyObj(options) : {};
+ // Determine effective options based on given values and defaults.
+ copyObj(defaults, options, false);
+
+ var doc = options.value;
+ if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); }
+ else if (options.mode) { doc.modeOption = options.mode; }
+ this.doc = doc;
+
+ var input = new CodeMirror.inputStyles[options.inputStyle](this);
+ var display = this.display = new Display(place, doc, input, options);
+ display.wrapper.CodeMirror = this;
+ themeChanged(this);
+ if (options.lineWrapping)
+ { this.display.wrapper.className += " CodeMirror-wrap"; }
+ initScrollbars(this);
+
+ this.state = {
+ keyMaps: [], // stores maps added by addKeyMap
+ overlays: [], // highlighting overlays, as added by addOverlay
+ modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
+ overwrite: false,
+ delayingBlurEvent: false,
+ focused: false,
+ suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
+ pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll
+ selectingText: false,
+ draggingText: false,
+ highlight: new Delayed(), // stores highlight worker timeout
+ keySeq: null, // Unfinished key sequence
+ specialChars: null
+ };
+
+ if (options.autofocus && !mobile) { display.input.focus(); }
+
+ // Override magic textarea content restore that IE sometimes does
+ // on our hidden textarea on reload
+ if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); }
+
+ registerEventHandlers(this);
+ ensureGlobalHandlers();
+
+ startOperation(this);
+ this.curOp.forceUpdate = true;
+ attachDoc(this, doc);
+
+ if ((options.autofocus && !mobile) || this.hasFocus())
+ { setTimeout(bind(onFocus, this), 20); }
+ else
+ { onBlur(this); }
+
+ for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt))
+ { optionHandlers[opt](this$1, options[opt], Init); } }
+ maybeUpdateLineNumberWidth(this);
+ if (options.finishInit) { options.finishInit(this); }
+ for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1); }
+ endOperation(this);
+ // Suppress optimizelegibility in Webkit, since it breaks text
+ // measuring on line wrapping boundaries.
+ if (webkit && options.lineWrapping &&
+ getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
+ { display.lineDiv.style.textRendering = "auto"; }
+ }
+
+ // The default configuration options.
+ CodeMirror.defaults = defaults;
+ // Functions to run when options are changed.
+ CodeMirror.optionHandlers = optionHandlers;
+
+ // Attach the necessary event handlers when initializing the editor
+ function registerEventHandlers(cm) {
+ var d = cm.display;
+ on(d.scroller, "mousedown", operation(cm, onMouseDown));
+ // Older IE's will not fire a second mousedown for a double click
+ if (ie && ie_version < 11)
+ { on(d.scroller, "dblclick", operation(cm, function (e) {
+ if (signalDOMEvent(cm, e)) { return }
+ var pos = posFromMouse(cm, e);
+ if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return }
+ e_preventDefault(e);
+ var word = cm.findWordAt(pos);
+ extendSelection(cm.doc, word.anchor, word.head);
+ })); }
+ else
+ { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); }
+ // Some browsers fire contextmenu *after* opening the menu, at
+ // which point we can't mess with it anymore. Context menu is
+ // handled in onMouseDown for these browsers.
+ on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); });
+
+ // Used to suppress mouse event handling when a touch happens
+ var touchFinished, prevTouch = {end: 0};
+ function finishTouch() {
+ if (d.activeTouch) {
+ touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000);
+ prevTouch = d.activeTouch;
+ prevTouch.end = +new Date;
+ }
+ }
+ function isMouseLikeTouchEvent(e) {
+ if (e.touches.length != 1) { return false }
+ var touch = e.touches[0];
+ return touch.radiusX <= 1 && touch.radiusY <= 1
+ }
+ function farAway(touch, other) {
+ if (other.left == null) { return true }
+ var dx = other.left - touch.left, dy = other.top - touch.top;
+ return dx * dx + dy * dy > 20 * 20
+ }
+ on(d.scroller, "touchstart", function (e) {
+ if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) {
+ d.input.ensurePolled();
+ clearTimeout(touchFinished);
+ var now = +new Date;
+ d.activeTouch = {start: now, moved: false,
+ prev: now - prevTouch.end <= 300 ? prevTouch : null};
+ if (e.touches.length == 1) {
+ d.activeTouch.left = e.touches[0].pageX;
+ d.activeTouch.top = e.touches[0].pageY;
+ }
+ }
+ });
+ on(d.scroller, "touchmove", function () {
+ if (d.activeTouch) { d.activeTouch.moved = true; }
+ });
+ on(d.scroller, "touchend", function (e) {
+ var touch = d.activeTouch;
+ if (touch && !eventInWidget(d, e) && touch.left != null &&
+ !touch.moved && new Date - touch.start < 300) {
+ var pos = cm.coordsChar(d.activeTouch, "page"), range;
+ if (!touch.prev || farAway(touch, touch.prev)) // Single tap
+ { range = new Range(pos, pos); }
+ else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap
+ { range = cm.findWordAt(pos); }
+ else // Triple tap
+ { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); }
+ cm.setSelection(range.anchor, range.head);
+ cm.focus();
+ e_preventDefault(e);
+ }
+ finishTouch();
+ });
+ on(d.scroller, "touchcancel", finishTouch);
+
+ // Sync scrolling between fake scrollbars and real scrollable
+ // area, ensure viewport is updated when scrolling.
+ on(d.scroller, "scroll", function () {
+ if (d.scroller.clientHeight) {
+ updateScrollTop(cm, d.scroller.scrollTop);
+ setScrollLeft(cm, d.scroller.scrollLeft, true);
+ signal(cm, "scroll", cm);
+ }
+ });
+
+ // Listen to wheel events in order to try and update the viewport on time.
+ on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); });
+ on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); });
+
+ // Prevent wrapper from ever scrolling
+ on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
+
+ d.dragFunctions = {
+ enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }},
+ over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }},
+ start: function (e) { return onDragStart(cm, e); },
+ drop: operation(cm, onDrop),
+ leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }}
+ };
+
+ var inp = d.input.getField();
+ on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); });
+ on(inp, "keydown", operation(cm, onKeyDown));
+ on(inp, "keypress", operation(cm, onKeyPress));
+ on(inp, "focus", function (e) { return onFocus(cm, e); });
+ on(inp, "blur", function (e) { return onBlur(cm, e); });
+ }
+
+ var initHooks = [];
+ CodeMirror.defineInitHook = function (f) { return initHooks.push(f); };
+
+ // Indent the given line. The how parameter can be "smart",
+ // "add"/null, "subtract", or "prev". When aggressive is false
+ // (typically set to true for forced single-line indents), empty
+ // lines are not indented, and places where the mode returns Pass
+ // are left alone.
+ function indentLine(cm, n, how, aggressive) {
+ var doc = cm.doc, state;
+ if (how == null) { how = "add"; }
+ if (how == "smart") {
+ // Fall back to "prev" when the mode doesn't have an indentation
+ // method.
+ if (!doc.mode.indent) { how = "prev"; }
+ else { state = getContextBefore(cm, n).state; }
+ }
+
+ var tabSize = cm.options.tabSize;
+ var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
+ if (line.stateAfter) { line.stateAfter = null; }
+ var curSpaceString = line.text.match(/^\s*/)[0], indentation;
+ if (!aggressive && !/\S/.test(line.text)) {
+ indentation = 0;
+ how = "not";
+ } else if (how == "smart") {
+ indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
+ if (indentation == Pass || indentation > 150) {
+ if (!aggressive) { return }
+ how = "prev";
+ }
+ }
+ if (how == "prev") {
+ if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize); }
+ else { indentation = 0; }
+ } else if (how == "add") {
+ indentation = curSpace + cm.options.indentUnit;
+ } else if (how == "subtract") {
+ indentation = curSpace - cm.options.indentUnit;
+ } else if (typeof how == "number") {
+ indentation = curSpace + how;
+ }
+ indentation = Math.max(0, indentation);
+
+ var indentString = "", pos = 0;
+ if (cm.options.indentWithTabs)
+ { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} }
+ if (pos < indentation) { indentString += spaceStr(indentation - pos); }
+
+ if (indentString != curSpaceString) {
+ replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
+ line.stateAfter = null;
+ return true
+ } else {
+ // Ensure that, if the cursor was in the whitespace at the start
+ // of the line, it is moved to the end of that space.
+ for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) {
+ var range = doc.sel.ranges[i$1];
+ if (range.head.line == n && range.head.ch < curSpaceString.length) {
+ var pos$1 = Pos(n, curSpaceString.length);
+ replaceOneSelection(doc, i$1, new Range(pos$1, pos$1));
+ break
+ }
+ }
+ }
+ }
+
+ // This will be set to a {lineWise: bool, text: [string]} object, so
+ // that, when pasting, we know what kind of selections the copied
+ // text was made out of.
+ var lastCopied = null;
+
+ function setLastCopied(newLastCopied) {
+ lastCopied = newLastCopied;
+ }
+
+ function applyTextInput(cm, inserted, deleted, sel, origin) {
+ var doc = cm.doc;
+ cm.display.shift = false;
+ if (!sel) { sel = doc.sel; }
+
+ var recent = +new Date - 200;
+ var paste = origin == "paste" || cm.state.pasteIncoming > recent;
+ var textLines = splitLinesAuto(inserted), multiPaste = null;
+ // When pasting N lines into N selections, insert one line per selection
+ if (paste && sel.ranges.length > 1) {
+ if (lastCopied && lastCopied.text.join("\n") == inserted) {
+ if (sel.ranges.length % lastCopied.text.length == 0) {
+ multiPaste = [];
+ for (var i = 0; i < lastCopied.text.length; i++)
+ { multiPaste.push(doc.splitLines(lastCopied.text[i])); }
+ }
+ } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) {
+ multiPaste = map(textLines, function (l) { return [l]; });
+ }
+ }
+
+ var updateInput = cm.curOp.updateInput;
+ // Normal behavior is to insert the new text into every selection
+ for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) {
+ var range$$1 = sel.ranges[i$1];
+ var from = range$$1.from(), to = range$$1.to();
+ if (range$$1.empty()) {
+ if (deleted && deleted > 0) // Handle deletion
+ { from = Pos(from.line, from.ch - deleted); }
+ else if (cm.state.overwrite && !paste) // Handle overwrite
+ { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); }
+ else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted)
+ { from = to = Pos(from.line, 0); }
+ }
+ var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines,
+ origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")};
+ makeChange(cm.doc, changeEvent);
+ signalLater(cm, "inputRead", cm, changeEvent);
+ }
+ if (inserted && !paste)
+ { triggerElectric(cm, inserted); }
+
+ ensureCursorVisible(cm);
+ if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; }
+ cm.curOp.typing = true;
+ cm.state.pasteIncoming = cm.state.cutIncoming = -1;
+ }
+
+ function handlePaste(e, cm) {
+ var pasted = e.clipboardData && e.clipboardData.getData("Text");
+ if (pasted) {
+ e.preventDefault();
+ if (!cm.isReadOnly() && !cm.options.disableInput)
+ { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }); }
+ return true
+ }
+ }
+
+ function triggerElectric(cm, inserted) {
+ // When an 'electric' character is inserted, immediately trigger a reindent
+ if (!cm.options.electricChars || !cm.options.smartIndent) { return }
+ var sel = cm.doc.sel;
+
+ for (var i = sel.ranges.length - 1; i >= 0; i--) {
+ var range$$1 = sel.ranges[i];
+ if (range$$1.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range$$1.head.line)) { continue }
+ var mode = cm.getModeAt(range$$1.head);
+ var indented = false;
+ if (mode.electricChars) {
+ for (var j = 0; j < mode.electricChars.length; j++)
+ { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
+ indented = indentLine(cm, range$$1.head.line, "smart");
+ break
+ } }
+ } else if (mode.electricInput) {
+ if (mode.electricInput.test(getLine(cm.doc, range$$1.head.line).text.slice(0, range$$1.head.ch)))
+ { indented = indentLine(cm, range$$1.head.line, "smart"); }
+ }
+ if (indented) { signalLater(cm, "electricInput", cm, range$$1.head.line); }
+ }
+ }
+
+ function copyableRanges(cm) {
+ var text = [], ranges = [];
+ for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
+ var line = cm.doc.sel.ranges[i].head.line;
+ var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
+ ranges.push(lineRange);
+ text.push(cm.getRange(lineRange.anchor, lineRange.head));
+ }
+ return {text: text, ranges: ranges}
+ }
+
+ function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {
+ field.setAttribute("autocorrect", autocorrect ? "" : "off");
+ field.setAttribute("autocapitalize", autocapitalize ? "" : "off");
+ field.setAttribute("spellcheck", !!spellcheck);
+ }
+
+ function hiddenTextarea() {
+ var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none");
+ var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
+ // The textarea is kept positioned near the cursor to prevent the
+ // fact that it'll be scrolled into view on input from scrolling
+ // our fake cursor out of view. On webkit, when wrap=off, paste is
+ // very slow. So make the area wide instead.
+ if (webkit) { te.style.width = "1000px"; }
+ else { te.setAttribute("wrap", "off"); }
+ // If border: 0; -- iOS fails to open keyboard (issue #1287)
+ if (ios) { te.style.border = "1px solid black"; }
+ disableBrowserMagic(te);
+ return div
+ }
+
+ // The publicly visible API. Note that methodOp(f) means
+ // 'wrap f in an operation, performed on its `this` parameter'.
+
+ // This is not the complete set of editor methods. Most of the
+ // methods defined on the Doc type are also injected into
+ // CodeMirror.prototype, for backwards compatibility and
+ // convenience.
+
+ function addEditorMethods(CodeMirror) {
+ var optionHandlers = CodeMirror.optionHandlers;
+
+ var helpers = CodeMirror.helpers = {};
+
+ CodeMirror.prototype = {
+ constructor: CodeMirror,
+ focus: function(){window.focus(); this.display.input.focus();},
+
+ setOption: function(option, value) {
+ var options = this.options, old = options[option];
+ if (options[option] == value && option != "mode") { return }
+ options[option] = value;
+ if (optionHandlers.hasOwnProperty(option))
+ { operation(this, optionHandlers[option])(this, value, old); }
+ signal(this, "optionChange", this, option);
+ },
+
+ getOption: function(option) {return this.options[option]},
+ getDoc: function() {return this.doc},
+
+ addKeyMap: function(map$$1, bottom) {
+ this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map$$1));
+ },
+ removeKeyMap: function(map$$1) {
+ var maps = this.state.keyMaps;
+ for (var i = 0; i < maps.length; ++i)
+ { if (maps[i] == map$$1 || maps[i].name == map$$1) {
+ maps.splice(i, 1);
+ return true
+ } }
+ },
+
+ addOverlay: methodOp(function(spec, options) {
+ var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
+ if (mode.startState) { throw new Error("Overlays may not be stateful.") }
+ insertSorted(this.state.overlays,
+ {mode: mode, modeSpec: spec, opaque: options && options.opaque,
+ priority: (options && options.priority) || 0},
+ function (overlay) { return overlay.priority; });
+ this.state.modeGen++;
+ regChange(this);
+ }),
+ removeOverlay: methodOp(function(spec) {
+ var this$1 = this;
+
+ var overlays = this.state.overlays;
+ for (var i = 0; i < overlays.length; ++i) {
+ var cur = overlays[i].modeSpec;
+ if (cur == spec || typeof spec == "string" && cur.name == spec) {
+ overlays.splice(i, 1);
+ this$1.state.modeGen++;
+ regChange(this$1);
+ return
+ }
+ }
+ }),
+
+ indentLine: methodOp(function(n, dir, aggressive) {
+ if (typeof dir != "string" && typeof dir != "number") {
+ if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev"; }
+ else { dir = dir ? "add" : "subtract"; }
+ }
+ if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive); }
+ }),
+ indentSelection: methodOp(function(how) {
+ var this$1 = this;
+
+ var ranges = this.doc.sel.ranges, end = -1;
+ for (var i = 0; i < ranges.length; i++) {
+ var range$$1 = ranges[i];
+ if (!range$$1.empty()) {
+ var from = range$$1.from(), to = range$$1.to();
+ var start = Math.max(end, from.line);
+ end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
+ for (var j = start; j < end; ++j)
+ { indentLine(this$1, j, how); }
+ var newRanges = this$1.doc.sel.ranges;
+ if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
+ { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); }
+ } else if (range$$1.head.line > end) {
+ indentLine(this$1, range$$1.head.line, how, true);
+ end = range$$1.head.line;
+ if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1); }
+ }
+ }
+ }),
+
+ // Fetch the parser token for a given character. Useful for hacks
+ // that want to inspect the mode state (say, for completion).
+ getTokenAt: function(pos, precise) {
+ return takeToken(this, pos, precise)
+ },
+
+ getLineTokens: function(line, precise) {
+ return takeToken(this, Pos(line), precise, true)
+ },
+
+ getTokenTypeAt: function(pos) {
+ pos = clipPos(this.doc, pos);
+ var styles = getLineStyles(this, getLine(this.doc, pos.line));
+ var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
+ var type;
+ if (ch == 0) { type = styles[2]; }
+ else { for (;;) {
+ var mid = (before + after) >> 1;
+ if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid; }
+ else if (styles[mid * 2 + 1] < ch) { before = mid + 1; }
+ else { type = styles[mid * 2 + 2]; break }
+ } }
+ var cut = type ? type.indexOf("overlay ") : -1;
+ return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1)
+ },
+
+ getModeAt: function(pos) {
+ var mode = this.doc.mode;
+ if (!mode.innerMode) { return mode }
+ return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode
+ },
+
+ getHelper: function(pos, type) {
+ return this.getHelpers(pos, type)[0]
+ },
+
+ getHelpers: function(pos, type) {
+ var this$1 = this;
+
+ var found = [];
+ if (!helpers.hasOwnProperty(type)) { return found }
+ var help = helpers[type], mode = this.getModeAt(pos);
+ if (typeof mode[type] == "string") {
+ if (help[mode[type]]) { found.push(help[mode[type]]); }
+ } else if (mode[type]) {
+ for (var i = 0; i < mode[type].length; i++) {
+ var val = help[mode[type][i]];
+ if (val) { found.push(val); }
+ }
+ } else if (mode.helperType && help[mode.helperType]) {
+ found.push(help[mode.helperType]);
+ } else if (help[mode.name]) {
+ found.push(help[mode.name]);
+ }
+ for (var i$1 = 0; i$1 < help._global.length; i$1++) {
+ var cur = help._global[i$1];
+ if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1)
+ { found.push(cur.val); }
+ }
+ return found
+ },
+
+ getStateAfter: function(line, precise) {
+ var doc = this.doc;
+ line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
+ return getContextBefore(this, line + 1, precise).state
+ },
+
+ cursorCoords: function(start, mode) {
+ var pos, range$$1 = this.doc.sel.primary();
+ if (start == null) { pos = range$$1.head; }
+ else if (typeof start == "object") { pos = clipPos(this.doc, start); }
+ else { pos = start ? range$$1.from() : range$$1.to(); }
+ return cursorCoords(this, pos, mode || "page")
+ },
+
+ charCoords: function(pos, mode) {
+ return charCoords(this, clipPos(this.doc, pos), mode || "page")
+ },
+
+ coordsChar: function(coords, mode) {
+ coords = fromCoordSystem(this, coords, mode || "page");
+ return coordsChar(this, coords.left, coords.top)
+ },
+
+ lineAtHeight: function(height, mode) {
+ height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
+ return lineAtHeight(this.doc, height + this.display.viewOffset)
+ },
+ heightAtLine: function(line, mode, includeWidgets) {
+ var end = false, lineObj;
+ if (typeof line == "number") {
+ var last = this.doc.first + this.doc.size - 1;
+ if (line < this.doc.first) { line = this.doc.first; }
+ else if (line > last) { line = last; end = true; }
+ lineObj = getLine(this.doc, line);
+ } else {
+ lineObj = line;
+ }
+ return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top +
+ (end ? this.doc.height - heightAtLine(lineObj) : 0)
+ },
+
+ defaultTextHeight: function() { return textHeight(this.display) },
+ defaultCharWidth: function() { return charWidth(this.display) },
+
+ getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}},
+
+ addWidget: function(pos, node, scroll, vert, horiz) {
+ var display = this.display;
+ pos = cursorCoords(this, clipPos(this.doc, pos));
+ var top = pos.bottom, left = pos.left;
+ node.style.position = "absolute";
+ node.setAttribute("cm-ignore-events", "true");
+ this.display.input.setUneditable(node);
+ display.sizer.appendChild(node);
+ if (vert == "over") {
+ top = pos.top;
+ } else if (vert == "above" || vert == "near") {
+ var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
+ hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
+ // Default to positioning above (if specified and possible); otherwise default to positioning below
+ if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
+ { top = pos.top - node.offsetHeight; }
+ else if (pos.bottom + node.offsetHeight <= vspace)
+ { top = pos.bottom; }
+ if (left + node.offsetWidth > hspace)
+ { left = hspace - node.offsetWidth; }
+ }
+ node.style.top = top + "px";
+ node.style.left = node.style.right = "";
+ if (horiz == "right") {
+ left = display.sizer.clientWidth - node.offsetWidth;
+ node.style.right = "0px";
+ } else {
+ if (horiz == "left") { left = 0; }
+ else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2; }
+ node.style.left = left + "px";
+ }
+ if (scroll)
+ { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); }
+ },
+
+ triggerOnKeyDown: methodOp(onKeyDown),
+ triggerOnKeyPress: methodOp(onKeyPress),
+ triggerOnKeyUp: onKeyUp,
+ triggerOnMouseDown: methodOp(onMouseDown),
+
+ execCommand: function(cmd) {
+ if (commands.hasOwnProperty(cmd))
+ { return commands[cmd].call(null, this) }
+ },
+
+ triggerElectric: methodOp(function(text) { triggerElectric(this, text); }),
+
+ findPosH: function(from, amount, unit, visually) {
+ var this$1 = this;
+
+ var dir = 1;
+ if (amount < 0) { dir = -1; amount = -amount; }
+ var cur = clipPos(this.doc, from);
+ for (var i = 0; i < amount; ++i) {
+ cur = findPosH(this$1.doc, cur, dir, unit, visually);
+ if (cur.hitSide) { break }
+ }
+ return cur
+ },
+
+ moveH: methodOp(function(dir, unit) {
+ var this$1 = this;
+
+ this.extendSelectionsBy(function (range$$1) {
+ if (this$1.display.shift || this$1.doc.extend || range$$1.empty())
+ { return findPosH(this$1.doc, range$$1.head, dir, unit, this$1.options.rtlMoveVisually) }
+ else
+ { return dir < 0 ? range$$1.from() : range$$1.to() }
+ }, sel_move);
+ }),
+
+ deleteH: methodOp(function(dir, unit) {
+ var sel = this.doc.sel, doc = this.doc;
+ if (sel.somethingSelected())
+ { doc.replaceSelection("", null, "+delete"); }
+ else
+ { deleteNearSelection(this, function (range$$1) {
+ var other = findPosH(doc, range$$1.head, dir, unit, false);
+ return dir < 0 ? {from: other, to: range$$1.head} : {from: range$$1.head, to: other}
+ }); }
+ }),
+
+ findPosV: function(from, amount, unit, goalColumn) {
+ var this$1 = this;
+
+ var dir = 1, x = goalColumn;
+ if (amount < 0) { dir = -1; amount = -amount; }
+ var cur = clipPos(this.doc, from);
+ for (var i = 0; i < amount; ++i) {
+ var coords = cursorCoords(this$1, cur, "div");
+ if (x == null) { x = coords.left; }
+ else { coords.left = x; }
+ cur = findPosV(this$1, coords, dir, unit);
+ if (cur.hitSide) { break }
+ }
+ return cur
+ },
+
+ moveV: methodOp(function(dir, unit) {
+ var this$1 = this;
+
+ var doc = this.doc, goals = [];
+ var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected();
+ doc.extendSelectionsBy(function (range$$1) {
+ if (collapse)
+ { return dir < 0 ? range$$1.from() : range$$1.to() }
+ var headPos = cursorCoords(this$1, range$$1.head, "div");
+ if (range$$1.goalColumn != null) { headPos.left = range$$1.goalColumn; }
+ goals.push(headPos.left);
+ var pos = findPosV(this$1, headPos, dir, unit);
+ if (unit == "page" && range$$1 == doc.sel.primary())
+ { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top); }
+ return pos
+ }, sel_move);
+ if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++)
+ { doc.sel.ranges[i].goalColumn = goals[i]; } }
+ }),
+
+ // Find the word at the given position (as returned by coordsChar).
+ findWordAt: function(pos) {
+ var doc = this.doc, line = getLine(doc, pos.line).text;
+ var start = pos.ch, end = pos.ch;
+ if (line) {
+ var helper = this.getHelper(pos, "wordChars");
+ if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end; }
+ var startChar = line.charAt(start);
+ var check = isWordChar(startChar, helper)
+ ? function (ch) { return isWordChar(ch, helper); }
+ : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); }
+ : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); };
+ while (start > 0 && check(line.charAt(start - 1))) { --start; }
+ while (end < line.length && check(line.charAt(end))) { ++end; }
+ }
+ return new Range(Pos(pos.line, start), Pos(pos.line, end))
+ },
+
+ toggleOverwrite: function(value) {
+ if (value != null && value == this.state.overwrite) { return }
+ if (this.state.overwrite = !this.state.overwrite)
+ { addClass(this.display.cursorDiv, "CodeMirror-overwrite"); }
+ else
+ { rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); }
+
+ signal(this, "overwriteToggle", this, this.state.overwrite);
+ },
+ hasFocus: function() { return this.display.input.getField() == activeElt() },
+ isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) },
+
+ scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }),
+ getScrollInfo: function() {
+ var scroller = this.display.scroller;
+ return {left: scroller.scrollLeft, top: scroller.scrollTop,
+ height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight,
+ width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth,
+ clientHeight: displayHeight(this), clientWidth: displayWidth(this)}
+ },
+
+ scrollIntoView: methodOp(function(range$$1, margin) {
+ if (range$$1 == null) {
+ range$$1 = {from: this.doc.sel.primary().head, to: null};
+ if (margin == null) { margin = this.options.cursorScrollMargin; }
+ } else if (typeof range$$1 == "number") {
+ range$$1 = {from: Pos(range$$1, 0), to: null};
+ } else if (range$$1.from == null) {
+ range$$1 = {from: range$$1, to: null};
+ }
+ if (!range$$1.to) { range$$1.to = range$$1.from; }
+ range$$1.margin = margin || 0;
+
+ if (range$$1.from.line != null) {
+ scrollToRange(this, range$$1);
+ } else {
+ scrollToCoordsRange(this, range$$1.from, range$$1.to, range$$1.margin);
+ }
+ }),
+
+ setSize: methodOp(function(width, height) {
+ var this$1 = this;
+
+ var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; };
+ if (width != null) { this.display.wrapper.style.width = interpret(width); }
+ if (height != null) { this.display.wrapper.style.height = interpret(height); }
+ if (this.options.lineWrapping) { clearLineMeasurementCache(this); }
+ var lineNo$$1 = this.display.viewFrom;
+ this.doc.iter(lineNo$$1, this.display.viewTo, function (line) {
+ if (line.widgets) { for (var i = 0; i < line.widgets.length; i++)
+ { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo$$1, "widget"); break } } }
+ ++lineNo$$1;
+ });
+ this.curOp.forceUpdate = true;
+ signal(this, "refresh", this);
+ }),
+
+ operation: function(f){return runInOp(this, f)},
+ startOperation: function(){return startOperation(this)},
+ endOperation: function(){return endOperation(this)},
+
+ refresh: methodOp(function() {
+ var oldHeight = this.display.cachedTextHeight;
+ regChange(this);
+ this.curOp.forceUpdate = true;
+ clearCaches(this);
+ scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop);
+ updateGutterSpace(this.display);
+ if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
+ { estimateLineHeights(this); }
+ signal(this, "refresh", this);
+ }),
+
+ swapDoc: methodOp(function(doc) {
+ var old = this.doc;
+ old.cm = null;
+ // Cancel the current text selection if any (#5821)
+ if (this.state.selectingText) { this.state.selectingText(); }
+ attachDoc(this, doc);
+ clearCaches(this);
+ this.display.input.reset();
+ scrollToCoords(this, doc.scrollLeft, doc.scrollTop);
+ this.curOp.forceScroll = true;
+ signalLater(this, "swapDoc", this, old);
+ return old
+ }),
+
+ phrase: function(phraseText) {
+ var phrases = this.options.phrases;
+ return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText
+ },
+
+ getInputField: function(){return this.display.input.getField()},
+ getWrapperElement: function(){return this.display.wrapper},
+ getScrollerElement: function(){return this.display.scroller},
+ getGutterElement: function(){return this.display.gutters}
+ };
+ eventMixin(CodeMirror);
+
+ CodeMirror.registerHelper = function(type, name, value) {
+ if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; }
+ helpers[type][name] = value;
+ };
+ CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {
+ CodeMirror.registerHelper(type, name, value);
+ helpers[type]._global.push({pred: predicate, val: value});
+ };
+ }
+
+ // Used for horizontal relative motion. Dir is -1 or 1 (left or
+ // right), unit can be "char", "column" (like char, but doesn't
+ // cross line boundaries), "word" (across next word), or "group" (to
+ // the start of next group of word or non-word-non-whitespace
+ // chars). The visually param controls whether, in right-to-left
+ // text, direction 1 means to move towards the next index in the
+ // string, or towards the character to the right of the current
+ // position. The resulting position will have a hitSide=true
+ // property if it reached the end of the document.
+ function findPosH(doc, pos, dir, unit, visually) {
+ var oldPos = pos;
+ var origDir = dir;
+ var lineObj = getLine(doc, pos.line);
+ function findNextLine() {
+ var l = pos.line + dir;
+ if (l < doc.first || l >= doc.first + doc.size) { return false }
+ pos = new Pos(l, pos.ch, pos.sticky);
+ return lineObj = getLine(doc, l)
+ }
+ function moveOnce(boundToLine) {
+ var next;
+ if (visually) {
+ next = moveVisually(doc.cm, lineObj, pos, dir);
+ } else {
+ next = moveLogically(lineObj, pos, dir);
+ }
+ if (next == null) {
+ if (!boundToLine && findNextLine())
+ { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir); }
+ else
+ { return false }
+ } else {
+ pos = next;
+ }
+ return true
+ }
+
+ if (unit == "char") {
+ moveOnce();
+ } else if (unit == "column") {
+ moveOnce(true);
+ } else if (unit == "word" || unit == "group") {
+ var sawType = null, group = unit == "group";
+ var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
+ for (var first = true;; first = false) {
+ if (dir < 0 && !moveOnce(!first)) { break }
+ var cur = lineObj.text.charAt(pos.ch) || "\n";
+ var type = isWordChar(cur, helper) ? "w"
+ : group && cur == "\n" ? "n"
+ : !group || /\s/.test(cur) ? null
+ : "p";
+ if (group && !first && !type) { type = "s"; }
+ if (sawType && sawType != type) {
+ if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after";}
+ break
+ }
+
+ if (type) { sawType = type; }
+ if (dir > 0 && !moveOnce(!first)) { break }
+ }
+ }
+ var result = skipAtomic(doc, pos, oldPos, origDir, true);
+ if (equalCursorPos(oldPos, result)) { result.hitSide = true; }
+ return result
+ }
+
+ // For relative vertical movement. Dir may be -1 or 1. Unit can be
+ // "page" or "line". The resulting position will have a hitSide=true
+ // property if it reached the end of the document.
+ function findPosV(cm, pos, dir, unit) {
+ var doc = cm.doc, x = pos.left, y;
+ if (unit == "page") {
+ var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
+ var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3);
+ y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount;
+
+ } else if (unit == "line") {
+ y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
+ }
+ var target;
+ for (;;) {
+ target = coordsChar(cm, x, y);
+ if (!target.outside) { break }
+ if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break }
+ y += dir * 5;
+ }
+ return target
+ }
+
+ // CONTENTEDITABLE INPUT STYLE
+
+ var ContentEditableInput = function(cm) {
+ this.cm = cm;
+ this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;
+ this.polling = new Delayed();
+ this.composing = null;
+ this.gracePeriod = false;
+ this.readDOMTimeout = null;
+ };
+
+ ContentEditableInput.prototype.init = function (display) {
+ var this$1 = this;
+
+ var input = this, cm = input.cm;
+ var div = input.div = display.lineDiv;
+ disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize);
+
+ on(div, "paste", function (e) {
+ if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
+ // IE doesn't fire input events, so we schedule a read for the pasted content in this way
+ if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); }
+ });
+
+ on(div, "compositionstart", function (e) {
+ this$1.composing = {data: e.data, done: false};
+ });
+ on(div, "compositionupdate", function (e) {
+ if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; }
+ });
+ on(div, "compositionend", function (e) {
+ if (this$1.composing) {
+ if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); }
+ this$1.composing.done = true;
+ }
+ });
+
+ on(div, "touchstart", function () { return input.forceCompositionEnd(); });
+
+ on(div, "input", function () {
+ if (!this$1.composing) { this$1.readFromDOMSoon(); }
+ });
+
+ function onCopyCut(e) {
+ if (signalDOMEvent(cm, e)) { return }
+ if (cm.somethingSelected()) {
+ setLastCopied({lineWise: false, text: cm.getSelections()});
+ if (e.type == "cut") { cm.replaceSelection("", null, "cut"); }
+ } else if (!cm.options.lineWiseCopyCut) {
+ return
+ } else {
+ var ranges = copyableRanges(cm);
+ setLastCopied({lineWise: true, text: ranges.text});
+ if (e.type == "cut") {
+ cm.operation(function () {
+ cm.setSelections(ranges.ranges, 0, sel_dontScroll);
+ cm.replaceSelection("", null, "cut");
+ });
+ }
+ }
+ if (e.clipboardData) {
+ e.clipboardData.clearData();
+ var content = lastCopied.text.join("\n");
+ // iOS exposes the clipboard API, but seems to discard content inserted into it
+ e.clipboardData.setData("Text", content);
+ if (e.clipboardData.getData("Text") == content) {
+ e.preventDefault();
+ return
+ }
+ }
+ // Old-fashioned briefly-focus-a-textarea hack
+ var kludge = hiddenTextarea(), te = kludge.firstChild;
+ cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
+ te.value = lastCopied.text.join("\n");
+ var hadFocus = document.activeElement;
+ selectInput(te);
+ setTimeout(function () {
+ cm.display.lineSpace.removeChild(kludge);
+ hadFocus.focus();
+ if (hadFocus == div) { input.showPrimarySelection(); }
+ }, 50);
+ }
+ on(div, "copy", onCopyCut);
+ on(div, "cut", onCopyCut);
+ };
+
+ ContentEditableInput.prototype.prepareSelection = function () {
+ var result = prepareSelection(this.cm, false);
+ result.focus = this.cm.state.focused;
+ return result
+ };
+
+ ContentEditableInput.prototype.showSelection = function (info, takeFocus) {
+ if (!info || !this.cm.display.view.length) { return }
+ if (info.focus || takeFocus) { this.showPrimarySelection(); }
+ this.showMultipleSelections(info);
+ };
+
+ ContentEditableInput.prototype.getSelection = function () {
+ return this.cm.display.wrapper.ownerDocument.getSelection()
+ };
+
+ ContentEditableInput.prototype.showPrimarySelection = function () {
+ var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary();
+ var from = prim.from(), to = prim.to();
+
+ if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) {
+ sel.removeAllRanges();
+ return
+ }
+
+ var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);
+ var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset);
+ if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
+ cmp(minPos(curAnchor, curFocus), from) == 0 &&
+ cmp(maxPos(curAnchor, curFocus), to) == 0)
+ { return }
+
+ var view = cm.display.view;
+ var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) ||
+ {node: view[0].measure.map[2], offset: 0};
+ var end = to.line < cm.display.viewTo && posToDOM(cm, to);
+ if (!end) {
+ var measure = view[view.length - 1].measure;
+ var map$$1 = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map;
+ end = {node: map$$1[map$$1.length - 1], offset: map$$1[map$$1.length - 2] - map$$1[map$$1.length - 3]};
+ }
+
+ if (!start || !end) {
+ sel.removeAllRanges();
+ return
+ }
+
+ var old = sel.rangeCount && sel.getRangeAt(0), rng;
+ try { rng = range(start.node, start.offset, end.offset, end.node); }
+ catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
+ if (rng) {
+ if (!gecko && cm.state.focused) {
+ sel.collapse(start.node, start.offset);
+ if (!rng.collapsed) {
+ sel.removeAllRanges();
+ sel.addRange(rng);
+ }
+ } else {
+ sel.removeAllRanges();
+ sel.addRange(rng);
+ }
+ if (old && sel.anchorNode == null) { sel.addRange(old); }
+ else if (gecko) { this.startGracePeriod(); }
+ }
+ this.rememberSelection();
+ };
+
+ ContentEditableInput.prototype.startGracePeriod = function () {
+ var this$1 = this;
+
+ clearTimeout(this.gracePeriod);
+ this.gracePeriod = setTimeout(function () {
+ this$1.gracePeriod = false;
+ if (this$1.selectionChanged())
+ { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); }
+ }, 20);
+ };
+
+ ContentEditableInput.prototype.showMultipleSelections = function (info) {
+ removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);
+ removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);
+ };
+
+ ContentEditableInput.prototype.rememberSelection = function () {
+ var sel = this.getSelection();
+ this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;
+ this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;
+ };
+
+ ContentEditableInput.prototype.selectionInEditor = function () {
+ var sel = this.getSelection();
+ if (!sel.rangeCount) { return false }
+ var node = sel.getRangeAt(0).commonAncestorContainer;
+ return contains(this.div, node)
+ };
+
+ ContentEditableInput.prototype.focus = function () {
+ if (this.cm.options.readOnly != "nocursor") {
+ if (!this.selectionInEditor())
+ { this.showSelection(this.prepareSelection(), true); }
+ this.div.focus();
+ }
+ };
+ ContentEditableInput.prototype.blur = function () { this.div.blur(); };
+ ContentEditableInput.prototype.getField = function () { return this.div };
+
+ ContentEditableInput.prototype.supportsTouch = function () { return true };
+
+ ContentEditableInput.prototype.receivedFocus = function () {
+ var input = this;
+ if (this.selectionInEditor())
+ { this.pollSelection(); }
+ else
+ { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); }
+
+ function poll() {
+ if (input.cm.state.focused) {
+ input.pollSelection();
+ input.polling.set(input.cm.options.pollInterval, poll);
+ }
+ }
+ this.polling.set(this.cm.options.pollInterval, poll);
+ };
+
+ ContentEditableInput.prototype.selectionChanged = function () {
+ var sel = this.getSelection();
+ return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
+ sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset
+ };
+
+ ContentEditableInput.prototype.pollSelection = function () {
+ if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return }
+ var sel = this.getSelection(), cm = this.cm;
+ // On Android Chrome (version 56, at least), backspacing into an
+ // uneditable block element will put the cursor in that element,
+ // and then, because it's not editable, hide the virtual keyboard.
+ // Because Android doesn't allow us to actually detect backspace
+ // presses in a sane way, this code checks for when that happens
+ // and simulates a backspace press in this case.
+ if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) {
+ this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs});
+ this.blur();
+ this.focus();
+ return
+ }
+ if (this.composing) { return }
+ this.rememberSelection();
+ var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);
+ var head = domToPos(cm, sel.focusNode, sel.focusOffset);
+ if (anchor && head) { runInOp(cm, function () {
+ setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll);
+ if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; }
+ }); }
+ };
+
+ ContentEditableInput.prototype.pollContent = function () {
+ if (this.readDOMTimeout != null) {
+ clearTimeout(this.readDOMTimeout);
+ this.readDOMTimeout = null;
+ }
+
+ var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();
+ var from = sel.from(), to = sel.to();
+ if (from.ch == 0 && from.line > cm.firstLine())
+ { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); }
+ if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine())
+ { to = Pos(to.line + 1, 0); }
+ if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false }
+
+ var fromIndex, fromLine, fromNode;
+ if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {
+ fromLine = lineNo(display.view[0].line);
+ fromNode = display.view[0].node;
+ } else {
+ fromLine = lineNo(display.view[fromIndex].line);
+ fromNode = display.view[fromIndex - 1].node.nextSibling;
+ }
+ var toIndex = findViewIndex(cm, to.line);
+ var toLine, toNode;
+ if (toIndex == display.view.length - 1) {
+ toLine = display.viewTo - 1;
+ toNode = display.lineDiv.lastChild;
+ } else {
+ toLine = lineNo(display.view[toIndex + 1].line) - 1;
+ toNode = display.view[toIndex + 1].node.previousSibling;
+ }
+
+ if (!fromNode) { return false }
+ var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));
+ var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length));
+ while (newText.length > 1 && oldText.length > 1) {
+ if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; }
+ else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; }
+ else { break }
+ }
+
+ var cutFront = 0, cutEnd = 0;
+ var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length);
+ while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))
+ { ++cutFront; }
+ var newBot = lst(newText), oldBot = lst(oldText);
+ var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
+ oldBot.length - (oldText.length == 1 ? cutFront : 0));
+ while (cutEnd < maxCutEnd &&
+ newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))
+ { ++cutEnd; }
+ // Try to move start of change to start of selection if ambiguous
+ if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) {
+ while (cutFront && cutFront > from.ch &&
+ newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) {
+ cutFront--;
+ cutEnd++;
+ }
+ }
+
+ newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "");
+ newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "");
+
+ var chFrom = Pos(fromLine, cutFront);
+ var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0);
+ if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
+ replaceRange(cm.doc, newText, chFrom, chTo, "+input");
+ return true
+ }
+ };
+
+ ContentEditableInput.prototype.ensurePolled = function () {
+ this.forceCompositionEnd();
+ };
+ ContentEditableInput.prototype.reset = function () {
+ this.forceCompositionEnd();
+ };
+ ContentEditableInput.prototype.forceCompositionEnd = function () {
+ if (!this.composing) { return }
+ clearTimeout(this.readDOMTimeout);
+ this.composing = null;
+ this.updateFromDOM();
+ this.div.blur();
+ this.div.focus();
+ };
+ ContentEditableInput.prototype.readFromDOMSoon = function () {
+ var this$1 = this;
+
+ if (this.readDOMTimeout != null) { return }
+ this.readDOMTimeout = setTimeout(function () {
+ this$1.readDOMTimeout = null;
+ if (this$1.composing) {
+ if (this$1.composing.done) { this$1.composing = null; }
+ else { return }
+ }
+ this$1.updateFromDOM();
+ }, 80);
+ };
+
+ ContentEditableInput.prototype.updateFromDOM = function () {
+ var this$1 = this;
+
+ if (this.cm.isReadOnly() || !this.pollContent())
+ { runInOp(this.cm, function () { return regChange(this$1.cm); }); }
+ };
+
+ ContentEditableInput.prototype.setUneditable = function (node) {
+ node.contentEditable = "false";
+ };
+
+ ContentEditableInput.prototype.onKeyPress = function (e) {
+ if (e.charCode == 0 || this.composing) { return }
+ e.preventDefault();
+ if (!this.cm.isReadOnly())
+ { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); }
+ };
+
+ ContentEditableInput.prototype.readOnlyChanged = function (val) {
+ this.div.contentEditable = String(val != "nocursor");
+ };
+
+ ContentEditableInput.prototype.onContextMenu = function () {};
+ ContentEditableInput.prototype.resetPosition = function () {};
+
+ ContentEditableInput.prototype.needsContentAttribute = true;
+
+ function posToDOM(cm, pos) {
+ var view = findViewForLine(cm, pos.line);
+ if (!view || view.hidden) { return null }
+ var line = getLine(cm.doc, pos.line);
+ var info = mapFromLineView(view, line, pos.line);
+
+ var order = getOrder(line, cm.doc.direction), side = "left";
+ if (order) {
+ var partPos = getBidiPartAt(order, pos.ch);
+ side = partPos % 2 ? "right" : "left";
+ }
+ var result = nodeAndOffsetInLineMap(info.map, pos.ch, side);
+ result.offset = result.collapse == "right" ? result.end : result.start;
+ return result
+ }
+
+ function isInGutter(node) {
+ for (var scan = node; scan; scan = scan.parentNode)
+ { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } }
+ return false
+ }
+
+ function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos }
+
+ function domTextBetween(cm, from, to, fromLine, toLine) {
+ var text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false;
+ function recognizeMarker(id) { return function (marker) { return marker.id == id; } }
+ function close() {
+ if (closing) {
+ text += lineSep;
+ if (extraLinebreak) { text += lineSep; }
+ closing = extraLinebreak = false;
+ }
+ }
+ function addText(str) {
+ if (str) {
+ close();
+ text += str;
+ }
+ }
+ function walk(node) {
+ if (node.nodeType == 1) {
+ var cmText = node.getAttribute("cm-text");
+ if (cmText) {
+ addText(cmText);
+ return
+ }
+ var markerID = node.getAttribute("cm-marker"), range$$1;
+ if (markerID) {
+ var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID));
+ if (found.length && (range$$1 = found[0].find(0)))
+ { addText(getBetween(cm.doc, range$$1.from, range$$1.to).join(lineSep)); }
+ return
+ }
+ if (node.getAttribute("contenteditable") == "false") { return }
+ var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName);
+ if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return }
+
+ if (isBlock) { close(); }
+ for (var i = 0; i < node.childNodes.length; i++)
+ { walk(node.childNodes[i]); }
+
+ if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; }
+ if (isBlock) { closing = true; }
+ } else if (node.nodeType == 3) {
+ addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " "));
+ }
+ }
+ for (;;) {
+ walk(from);
+ if (from == to) { break }
+ from = from.nextSibling;
+ extraLinebreak = false;
+ }
+ return text
+ }
+
+ function domToPos(cm, node, offset) {
+ var lineNode;
+ if (node == cm.display.lineDiv) {
+ lineNode = cm.display.lineDiv.childNodes[offset];
+ if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) }
+ node = null; offset = 0;
+ } else {
+ for (lineNode = node;; lineNode = lineNode.parentNode) {
+ if (!lineNode || lineNode == cm.display.lineDiv) { return null }
+ if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break }
+ }
+ }
+ for (var i = 0; i < cm.display.view.length; i++) {
+ var lineView = cm.display.view[i];
+ if (lineView.node == lineNode)
+ { return locateNodeInLineView(lineView, node, offset) }
+ }
+ }
+
+ function locateNodeInLineView(lineView, node, offset) {
+ var wrapper = lineView.text.firstChild, bad = false;
+ if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) }
+ if (node == wrapper) {
+ bad = true;
+ node = wrapper.childNodes[offset];
+ offset = 0;
+ if (!node) {
+ var line = lineView.rest ? lst(lineView.rest) : lineView.line;
+ return badPos(Pos(lineNo(line), line.text.length), bad)
+ }
+ }
+
+ var textNode = node.nodeType == 3 ? node : null, topNode = node;
+ if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
+ textNode = node.firstChild;
+ if (offset) { offset = textNode.nodeValue.length; }
+ }
+ while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; }
+ var measure = lineView.measure, maps = measure.maps;
+
+ function find(textNode, topNode, offset) {
+ for (var i = -1; i < (maps ? maps.length : 0); i++) {
+ var map$$1 = i < 0 ? measure.map : maps[i];
+ for (var j = 0; j < map$$1.length; j += 3) {
+ var curNode = map$$1[j + 2];
+ if (curNode == textNode || curNode == topNode) {
+ var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);
+ var ch = map$$1[j] + offset;
+ if (offset < 0 || curNode != textNode) { ch = map$$1[j + (offset ? 1 : 0)]; }
+ return Pos(line, ch)
+ }
+ }
+ }
+ }
+ var found = find(textNode, topNode, offset);
+ if (found) { return badPos(found, bad) }
+
+ // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems
+ for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {
+ found = find(after, after.firstChild, 0);
+ if (found)
+ { return badPos(Pos(found.line, found.ch - dist), bad) }
+ else
+ { dist += after.textContent.length; }
+ }
+ for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) {
+ found = find(before, before.firstChild, -1);
+ if (found)
+ { return badPos(Pos(found.line, found.ch + dist$1), bad) }
+ else
+ { dist$1 += before.textContent.length; }
+ }
+ }
+
+ // TEXTAREA INPUT STYLE
+
+ var TextareaInput = function(cm) {
+ this.cm = cm;
+ // See input.poll and input.reset
+ this.prevInput = "";
+
+ // Flag that indicates whether we expect input to appear real soon
+ // now (after some event like 'keypress' or 'input') and are
+ // polling intensively.
+ this.pollingFast = false;
+ // Self-resetting timeout for the poller
+ this.polling = new Delayed();
+ // Used to work around IE issue with selection being forgotten when focus moves away from textarea
+ this.hasSelection = false;
+ this.composing = null;
+ };
+
+ TextareaInput.prototype.init = function (display) {
+ var this$1 = this;
+
+ var input = this, cm = this.cm;
+ this.createField(display);
+ var te = this.textarea;
+
+ display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild);
+
+ // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
+ if (ios) { te.style.width = "0px"; }
+
+ on(te, "input", function () {
+ if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; }
+ input.poll();
+ });
+
+ on(te, "paste", function (e) {
+ if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
+
+ cm.state.pasteIncoming = +new Date;
+ input.fastPoll();
+ });
+
+ function prepareCopyCut(e) {
+ if (signalDOMEvent(cm, e)) { return }
+ if (cm.somethingSelected()) {
+ setLastCopied({lineWise: false, text: cm.getSelections()});
+ } else if (!cm.options.lineWiseCopyCut) {
+ return
+ } else {
+ var ranges = copyableRanges(cm);
+ setLastCopied({lineWise: true, text: ranges.text});
+ if (e.type == "cut") {
+ cm.setSelections(ranges.ranges, null, sel_dontScroll);
+ } else {
+ input.prevInput = "";
+ te.value = ranges.text.join("\n");
+ selectInput(te);
+ }
+ }
+ if (e.type == "cut") { cm.state.cutIncoming = +new Date; }
+ }
+ on(te, "cut", prepareCopyCut);
+ on(te, "copy", prepareCopyCut);
+
+ on(display.scroller, "paste", function (e) {
+ if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }
+ if (!te.dispatchEvent) {
+ cm.state.pasteIncoming = +new Date;
+ input.focus();
+ return
+ }
+
+ // Pass the `paste` event to the textarea so it's handled by its event listener.
+ var event = new Event("paste");
+ event.clipboardData = e.clipboardData;
+ te.dispatchEvent(event);
+ });
+
+ // Prevent normal selection in the editor (we handle our own)
+ on(display.lineSpace, "selectstart", function (e) {
+ if (!eventInWidget(display, e)) { e_preventDefault(e); }
+ });
+
+ on(te, "compositionstart", function () {
+ var start = cm.getCursor("from");
+ if (input.composing) { input.composing.range.clear(); }
+ input.composing = {
+ start: start,
+ range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
+ };
+ });
+ on(te, "compositionend", function () {
+ if (input.composing) {
+ input.poll();
+ input.composing.range.clear();
+ input.composing = null;
+ }
+ });
+ };
+
+ TextareaInput.prototype.createField = function (_display) {
+ // Wraps and hides input textarea
+ this.wrapper = hiddenTextarea();
+ // The semihidden textarea that is focused when the editor is
+ // focused, and receives input.
+ this.textarea = this.wrapper.firstChild;
+ };
+
+ TextareaInput.prototype.prepareSelection = function () {
+ // Redraw the selection and/or cursor
+ var cm = this.cm, display = cm.display, doc = cm.doc;
+ var result = prepareSelection(cm);
+
+ // Move the hidden textarea near the cursor to prevent scrolling artifacts
+ if (cm.options.moveInputWithCursor) {
+ var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
+ var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
+ result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
+ headPos.top + lineOff.top - wrapOff.top));
+ result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
+ headPos.left + lineOff.left - wrapOff.left));
+ }
+
+ return result
+ };
+
+ TextareaInput.prototype.showSelection = function (drawn) {
+ var cm = this.cm, display = cm.display;
+ removeChildrenAndAdd(display.cursorDiv, drawn.cursors);
+ removeChildrenAndAdd(display.selectionDiv, drawn.selection);
+ if (drawn.teTop != null) {
+ this.wrapper.style.top = drawn.teTop + "px";
+ this.wrapper.style.left = drawn.teLeft + "px";
+ }
+ };
+
+ // Reset the input to correspond to the selection (or to be empty,
+ // when not typing and nothing is selected)
+ TextareaInput.prototype.reset = function (typing) {
+ if (this.contextMenuPending || this.composing) { return }
+ var cm = this.cm;
+ if (cm.somethingSelected()) {
+ this.prevInput = "";
+ var content = cm.getSelection();
+ this.textarea.value = content;
+ if (cm.state.focused) { selectInput(this.textarea); }
+ if (ie && ie_version >= 9) { this.hasSelection = content; }
+ } else if (!typing) {
+ this.prevInput = this.textarea.value = "";
+ if (ie && ie_version >= 9) { this.hasSelection = null; }
+ }
+ };
+
+ TextareaInput.prototype.getField = function () { return this.textarea };
+
+ TextareaInput.prototype.supportsTouch = function () { return false };
+
+ TextareaInput.prototype.focus = function () {
+ if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
+ try { this.textarea.focus(); }
+ catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
+ }
+ };
+
+ TextareaInput.prototype.blur = function () { this.textarea.blur(); };
+
+ TextareaInput.prototype.resetPosition = function () {
+ this.wrapper.style.top = this.wrapper.style.left = 0;
+ };
+
+ TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); };
+
+ // Poll for input changes, using the normal rate of polling. This
+ // runs as long as the editor is focused.
+ TextareaInput.prototype.slowPoll = function () {
+ var this$1 = this;
+
+ if (this.pollingFast) { return }
+ this.polling.set(this.cm.options.pollInterval, function () {
+ this$1.poll();
+ if (this$1.cm.state.focused) { this$1.slowPoll(); }
+ });
+ };
+
+ // When an event has just come in that is likely to add or change
+ // something in the input textarea, we poll faster, to ensure that
+ // the change appears on the screen quickly.
+ TextareaInput.prototype.fastPoll = function () {
+ var missed = false, input = this;
+ input.pollingFast = true;
+ function p() {
+ var changed = input.poll();
+ if (!changed && !missed) {missed = true; input.polling.set(60, p);}
+ else {input.pollingFast = false; input.slowPoll();}
+ }
+ input.polling.set(20, p);
+ };
+
+ // Read input from the textarea, and update the document to match.
+ // When something is selected, it is present in the textarea, and
+ // selected (unless it is huge, in which case a placeholder is
+ // used). When nothing is selected, the cursor sits after previously
+ // seen text (can be empty), which is stored in prevInput (we must
+ // not reset the textarea when typing, because that breaks IME).
+ TextareaInput.prototype.poll = function () {
+ var this$1 = this;
+
+ var cm = this.cm, input = this.textarea, prevInput = this.prevInput;
+ // Since this is called a *lot*, try to bail out as cheaply as
+ // possible when it is clear that nothing happened. hasSelection
+ // will be the case when there is a lot of text in the textarea,
+ // in which case reading its value would be expensive.
+ if (this.contextMenuPending || !cm.state.focused ||
+ (hasSelection(input) && !prevInput && !this.composing) ||
+ cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
+ { return false }
+
+ var text = input.value;
+ // If nothing changed, bail.
+ if (text == prevInput && !cm.somethingSelected()) { return false }
+ // Work around nonsensical selection resetting in IE9/10, and
+ // inexplicable appearance of private area unicode characters on
+ // some key combos in Mac (#2689).
+ if (ie && ie_version >= 9 && this.hasSelection === text ||
+ mac && /[\uf700-\uf7ff]/.test(text)) {
+ cm.display.input.reset();
+ return false
+ }
+
+ if (cm.doc.sel == cm.display.selForContextMenu) {
+ var first = text.charCodeAt(0);
+ if (first == 0x200b && !prevInput) { prevInput = "\u200b"; }
+ if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") }
+ }
+ // Find the part of the input that is actually new
+ var same = 0, l = Math.min(prevInput.length, text.length);
+ while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; }
+
+ runInOp(cm, function () {
+ applyTextInput(cm, text.slice(same), prevInput.length - same,
+ null, this$1.composing ? "*compose" : null);
+
+ // Don't leave long text in the textarea, since it makes further polling slow
+ if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = ""; }
+ else { this$1.prevInput = text; }
+
+ if (this$1.composing) {
+ this$1.composing.range.clear();
+ this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"),
+ {className: "CodeMirror-composing"});
+ }
+ });
+ return true
+ };
+
+ TextareaInput.prototype.ensurePolled = function () {
+ if (this.pollingFast && this.poll()) { this.pollingFast = false; }
+ };
+
+ TextareaInput.prototype.onKeyPress = function () {
+ if (ie && ie_version >= 9) { this.hasSelection = null; }
+ this.fastPoll();
+ };
+
+ TextareaInput.prototype.onContextMenu = function (e) {
+ var input = this, cm = input.cm, display = cm.display, te = input.textarea;
+ if (input.contextMenuPending) { input.contextMenuPending(); }
+ var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
+ if (!pos || presto) { return } // Opera is difficult.
+
+ // Reset the current text selection only if the click is done outside of the selection
+ // and 'resetSelectionOnContextMenu' option is true.
+ var reset = cm.options.resetSelectionOnContextMenu;
+ if (reset && cm.doc.sel.contains(pos) == -1)
+ { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); }
+
+ var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText;
+ var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect();
+ input.wrapper.style.cssText = "position: static";
+ te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
+ var oldScrollY;
+ if (webkit) { oldScrollY = window.scrollY; } // Work around Chrome issue (#2712)
+ display.input.focus();
+ if (webkit) { window.scrollTo(null, oldScrollY); }
+ display.input.reset();
+ // Adds "Select all" to context menu in FF
+ if (!cm.somethingSelected()) { te.value = input.prevInput = " "; }
+ input.contextMenuPending = rehide;
+ display.selForContextMenu = cm.doc.sel;
+ clearTimeout(display.detectingSelectAll);
+
+ // Select-all will be greyed out if there's nothing to select, so
+ // this adds a zero-width space so that we can later check whether
+ // it got selected.
+ function prepareSelectAllHack() {
+ if (te.selectionStart != null) {
+ var selected = cm.somethingSelected();
+ var extval = "\u200b" + (selected ? te.value : "");
+ te.value = "\u21da"; // Used to catch context-menu undo
+ te.value = extval;
+ input.prevInput = selected ? "" : "\u200b";
+ te.selectionStart = 1; te.selectionEnd = extval.length;
+ // Re-set this, in case some other handler touched the
+ // selection in the meantime.
+ display.selForContextMenu = cm.doc.sel;
+ }
+ }
+ function rehide() {
+ if (input.contextMenuPending != rehide) { return }
+ input.contextMenuPending = false;
+ input.wrapper.style.cssText = oldWrapperCSS;
+ te.style.cssText = oldCSS;
+ if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); }
+
+ // Try to detect the user choosing select-all
+ if (te.selectionStart != null) {
+ if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); }
+ var i = 0, poll = function () {
+ if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&
+ te.selectionEnd > 0 && input.prevInput == "\u200b") {
+ operation(cm, selectAll)(cm);
+ } else if (i++ < 10) {
+ display.detectingSelectAll = setTimeout(poll, 500);
+ } else {
+ display.selForContextMenu = null;
+ display.input.reset();
+ }
+ };
+ display.detectingSelectAll = setTimeout(poll, 200);
+ }
+ }
+
+ if (ie && ie_version >= 9) { prepareSelectAllHack(); }
+ if (captureRightClick) {
+ e_stop(e);
+ var mouseup = function () {
+ off(window, "mouseup", mouseup);
+ setTimeout(rehide, 20);
+ };
+ on(window, "mouseup", mouseup);
+ } else {
+ setTimeout(rehide, 50);
+ }
+ };
+
+ TextareaInput.prototype.readOnlyChanged = function (val) {
+ if (!val) { this.reset(); }
+ this.textarea.disabled = val == "nocursor";
+ };
+
+ TextareaInput.prototype.setUneditable = function () {};
+
+ TextareaInput.prototype.needsContentAttribute = false;
+
+ function fromTextArea(textarea, options) {
+ options = options ? copyObj(options) : {};
+ options.value = textarea.value;
+ if (!options.tabindex && textarea.tabIndex)
+ { options.tabindex = textarea.tabIndex; }
+ if (!options.placeholder && textarea.placeholder)
+ { options.placeholder = textarea.placeholder; }
+ // Set autofocus to true if this textarea is focused, or if it has
+ // autofocus and no other element is focused.
+ if (options.autofocus == null) {
+ var hasFocus = activeElt();
+ options.autofocus = hasFocus == textarea ||
+ textarea.getAttribute("autofocus") != null && hasFocus == document.body;
+ }
+
+ function save() {textarea.value = cm.getValue();}
+
+ var realSubmit;
+ if (textarea.form) {
+ on(textarea.form, "submit", save);
+ // Deplorable hack to make the submit method do the right thing.
+ if (!options.leaveSubmitMethodAlone) {
+ var form = textarea.form;
+ realSubmit = form.submit;
+ try {
+ var wrappedSubmit = form.submit = function () {
+ save();
+ form.submit = realSubmit;
+ form.submit();
+ form.submit = wrappedSubmit;
+ };
+ } catch(e) {}
+ }
+ }
+
+ options.finishInit = function (cm) {
+ cm.save = save;
+ cm.getTextArea = function () { return textarea; };
+ cm.toTextArea = function () {
+ cm.toTextArea = isNaN; // Prevent this from being ran twice
+ save();
+ textarea.parentNode.removeChild(cm.getWrapperElement());
+ textarea.style.display = "";
+ if (textarea.form) {
+ off(textarea.form, "submit", save);
+ if (!options.leaveSubmitMethodAlone && typeof textarea.form.submit == "function")
+ { textarea.form.submit = realSubmit; }
+ }
+ };
+ };
+
+ textarea.style.display = "none";
+ var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); },
+ options);
+ return cm
+ }
+
+ function addLegacyProps(CodeMirror) {
+ CodeMirror.off = off;
+ CodeMirror.on = on;
+ CodeMirror.wheelEventPixels = wheelEventPixels;
+ CodeMirror.Doc = Doc;
+ CodeMirror.splitLines = splitLinesAuto;
+ CodeMirror.countColumn = countColumn;
+ CodeMirror.findColumn = findColumn;
+ CodeMirror.isWordChar = isWordCharBasic;
+ CodeMirror.Pass = Pass;
+ CodeMirror.signal = signal;
+ CodeMirror.Line = Line;
+ CodeMirror.changeEnd = changeEnd;
+ CodeMirror.scrollbarModel = scrollbarModel;
+ CodeMirror.Pos = Pos;
+ CodeMirror.cmpPos = cmp;
+ CodeMirror.modes = modes;
+ CodeMirror.mimeModes = mimeModes;
+ CodeMirror.resolveMode = resolveMode;
+ CodeMirror.getMode = getMode;
+ CodeMirror.modeExtensions = modeExtensions;
+ CodeMirror.extendMode = extendMode;
+ CodeMirror.copyState = copyState;
+ CodeMirror.startState = startState;
+ CodeMirror.innerMode = innerMode;
+ CodeMirror.commands = commands;
+ CodeMirror.keyMap = keyMap;
+ CodeMirror.keyName = keyName;
+ CodeMirror.isModifierKey = isModifierKey;
+ CodeMirror.lookupKey = lookupKey;
+ CodeMirror.normalizeKeyMap = normalizeKeyMap;
+ CodeMirror.StringStream = StringStream;
+ CodeMirror.SharedTextMarker = SharedTextMarker;
+ CodeMirror.TextMarker = TextMarker;
+ CodeMirror.LineWidget = LineWidget;
+ CodeMirror.e_preventDefault = e_preventDefault;
+ CodeMirror.e_stopPropagation = e_stopPropagation;
+ CodeMirror.e_stop = e_stop;
+ CodeMirror.addClass = addClass;
+ CodeMirror.contains = contains;
+ CodeMirror.rmClass = rmClass;
+ CodeMirror.keyNames = keyNames;
+ }
+
+ // EDITOR CONSTRUCTOR
+
+ defineOptions(CodeMirror);
+
+ addEditorMethods(CodeMirror);
+
+ // Set up methods on CodeMirror's prototype to redirect to the editor's document.
+ var dontDelegate = "iter insert remove copy getEditor constructor".split(" ");
+ for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
+ { CodeMirror.prototype[prop] = (function(method) {
+ return function() {return method.apply(this.doc, arguments)}
+ })(Doc.prototype[prop]); } }
+
+ eventMixin(Doc);
+ CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput};
+
+ // Extra arguments are stored as the mode's dependencies, which is
+ // used by (legacy) mechanisms like loadmode.js to automatically
+ // load a mode. (Preferred mechanism is the require/define calls.)
+ CodeMirror.defineMode = function(name/*, mode, …*/) {
+ if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name; }
+ defineMode.apply(this, arguments);
+ };
+
+ CodeMirror.defineMIME = defineMIME;
+
+ // Minimal default mode.
+ CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); });
+ CodeMirror.defineMIME("text/plain", "null");
+
+ // EXTENSIONS
+
+ CodeMirror.defineExtension = function (name, func) {
+ CodeMirror.prototype[name] = func;
+ };
+ CodeMirror.defineDocExtension = function (name, func) {
+ Doc.prototype[name] = func;
+ };
+
+ CodeMirror.fromTextArea = fromTextArea;
+
+ addLegacyProps(CodeMirror);
+
+ CodeMirror.version = "5.49.0";
+
+ return CodeMirror;
+
+})));
--- /dev/null
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"))
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod)
+ else // Plain browser env
+ mod(CodeMirror)
+})(function(CodeMirror) {
+ "use strict"
+ var Pos = CodeMirror.Pos
+
+ function regexpFlags(regexp) {
+ var flags = regexp.flags
+ return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
+ + (regexp.global ? "g" : "")
+ + (regexp.multiline ? "m" : "")
+ }
+
+ function ensureFlags(regexp, flags) {
+ var current = regexpFlags(regexp), target = current
+ for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
+ target += flags.charAt(i)
+ return current == target ? regexp : new RegExp(regexp.source, target)
+ }
+
+ function maybeMultiline(regexp) {
+ return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
+ }
+
+ function searchRegexpForward(doc, regexp, start) {
+ regexp = ensureFlags(regexp, "g")
+ for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
+ regexp.lastIndex = ch
+ var string = doc.getLine(line), match = regexp.exec(string)
+ if (match)
+ return {from: Pos(line, match.index),
+ to: Pos(line, match.index + match[0].length),
+ match: match}
+ }
+ }
+
+ function searchRegexpForwardMultiline(doc, regexp, start) {
+ if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
+
+ regexp = ensureFlags(regexp, "gm")
+ var string, chunk = 1
+ for (var line = start.line, last = doc.lastLine(); line <= last;) {
+ // This grows the search buffer in exponentially-sized chunks
+ // between matches, so that nearby matches are fast and don't
+ // require concatenating the whole document (in case we're
+ // searching for something that has tons of matches), but at the
+ // same time, the amount of retries is limited.
+ for (var i = 0; i < chunk; i++) {
+ if (line > last) break
+ var curLine = doc.getLine(line++)
+ string = string == null ? curLine : string + "\n" + curLine
+ }
+ chunk = chunk * 2
+ regexp.lastIndex = start.ch
+ var match = regexp.exec(string)
+ if (match) {
+ var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
+ var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
+ return {from: Pos(startLine, startCh),
+ to: Pos(startLine + inside.length - 1,
+ inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
+ match: match}
+ }
+ }
+ }
+
+ function lastMatchIn(string, regexp) {
+ var cutOff = 0, match
+ for (;;) {
+ regexp.lastIndex = cutOff
+ var newMatch = regexp.exec(string)
+ if (!newMatch) return match
+ match = newMatch
+ cutOff = match.index + (match[0].length || 1)
+ if (cutOff == string.length) return match
+ }
+ }
+
+ function searchRegexpBackward(doc, regexp, start) {
+ regexp = ensureFlags(regexp, "g")
+ for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
+ var string = doc.getLine(line)
+ if (ch > -1) string = string.slice(0, ch)
+ var match = lastMatchIn(string, regexp)
+ if (match)
+ return {from: Pos(line, match.index),
+ to: Pos(line, match.index + match[0].length),
+ match: match}
+ }
+ }
+
+ function searchRegexpBackwardMultiline(doc, regexp, start) {
+ regexp = ensureFlags(regexp, "gm")
+ var string, chunk = 1
+ for (var line = start.line, first = doc.firstLine(); line >= first;) {
+ for (var i = 0; i < chunk; i++) {
+ var curLine = doc.getLine(line--)
+ string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
+ }
+ chunk *= 2
+
+ var match = lastMatchIn(string, regexp)
+ if (match) {
+ var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
+ var startLine = line + before.length, startCh = before[before.length - 1].length
+ return {from: Pos(startLine, startCh),
+ to: Pos(startLine + inside.length - 1,
+ inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
+ match: match}
+ }
+ }
+ }
+
+ var doFold, noFold
+ if (String.prototype.normalize) {
+ doFold = function(str) { return str.normalize("NFD").toLowerCase() }
+ noFold = function(str) { return str.normalize("NFD") }
+ } else {
+ doFold = function(str) { return str.toLowerCase() }
+ noFold = function(str) { return str }
+ }
+
+ // Maps a position in a case-folded line back to a position in the original line
+ // (compensating for codepoints increasing in number during folding)
+ function adjustPos(orig, folded, pos, foldFunc) {
+ if (orig.length == folded.length) return pos
+ for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
+ if (min == max) return min
+ var mid = (min + max) >> 1
+ var len = foldFunc(orig.slice(0, mid)).length
+ if (len == pos) return mid
+ else if (len > pos) max = mid
+ else min = mid + 1
+ }
+ }
+
+ function searchStringForward(doc, query, start, caseFold) {
+ // Empty string would match anything and never progress, so we
+ // define it to match nothing instead.
+ if (!query.length) return null
+ var fold = caseFold ? doFold : noFold
+ var lines = fold(query).split(/\r|\n\r?/)
+
+ search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
+ var orig = doc.getLine(line).slice(ch), string = fold(orig)
+ if (lines.length == 1) {
+ var found = string.indexOf(lines[0])
+ if (found == -1) continue search
+ var start = adjustPos(orig, string, found, fold) + ch
+ return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
+ to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
+ } else {
+ var cutFrom = string.length - lines[0].length
+ if (string.slice(cutFrom) != lines[0]) continue search
+ for (var i = 1; i < lines.length - 1; i++)
+ if (fold(doc.getLine(line + i)) != lines[i]) continue search
+ var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
+ if (endString.slice(0, lastLine.length) != lastLine) continue search
+ return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
+ to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
+ }
+ }
+ }
+
+ function searchStringBackward(doc, query, start, caseFold) {
+ if (!query.length) return null
+ var fold = caseFold ? doFold : noFold
+ var lines = fold(query).split(/\r|\n\r?/)
+
+ search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
+ var orig = doc.getLine(line)
+ if (ch > -1) orig = orig.slice(0, ch)
+ var string = fold(orig)
+ if (lines.length == 1) {
+ var found = string.lastIndexOf(lines[0])
+ if (found == -1) continue search
+ return {from: Pos(line, adjustPos(orig, string, found, fold)),
+ to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
+ } else {
+ var lastLine = lines[lines.length - 1]
+ if (string.slice(0, lastLine.length) != lastLine) continue search
+ for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
+ if (fold(doc.getLine(start + i)) != lines[i]) continue search
+ var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
+ if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
+ return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
+ to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
+ }
+ }
+ }
+
+ function SearchCursor(doc, query, pos, options) {
+ this.atOccurrence = false
+ this.doc = doc
+ pos = pos ? doc.clipPos(pos) : Pos(0, 0)
+ this.pos = {from: pos, to: pos}
+
+ var caseFold
+ if (typeof options == "object") {
+ caseFold = options.caseFold
+ } else { // Backwards compat for when caseFold was the 4th argument
+ caseFold = options
+ options = null
+ }
+
+ if (typeof query == "string") {
+ if (caseFold == null) caseFold = false
+ this.matches = function(reverse, pos) {
+ return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
+ }
+ } else {
+ query = ensureFlags(query, "gm")
+ if (!options || options.multiline !== false)
+ this.matches = function(reverse, pos) {
+ return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
+ }
+ else
+ this.matches = function(reverse, pos) {
+ return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
+ }
+ }
+ }
+
+ SearchCursor.prototype = {
+ findNext: function() {return this.find(false)},
+ findPrevious: function() {return this.find(true)},
+
+ find: function(reverse) {
+ var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
+
+ // Implements weird auto-growing behavior on null-matches for
+ // backwards-compatiblity with the vim code (unfortunately)
+ while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
+ if (reverse) {
+ if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
+ else if (result.from.line == this.doc.firstLine()) result = null
+ else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
+ } else {
+ if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
+ else if (result.to.line == this.doc.lastLine()) result = null
+ else result = this.matches(reverse, Pos(result.to.line + 1, 0))
+ }
+ }
+
+ if (result) {
+ this.pos = result
+ this.atOccurrence = true
+ return this.pos.match || true
+ } else {
+ var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
+ this.pos = {from: end, to: end}
+ return this.atOccurrence = false
+ }
+ },
+
+ from: function() {if (this.atOccurrence) return this.pos.from},
+ to: function() {if (this.atOccurrence) return this.pos.to},
+
+ replace: function(newText, origin) {
+ if (!this.atOccurrence) return
+ var lines = CodeMirror.splitLines(newText)
+ this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
+ this.pos.to = Pos(this.pos.from.line + lines.length - 1,
+ lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
+ }
+ }
+
+ CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
+ return new SearchCursor(this.doc, query, pos, caseFold)
+ })
+ CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
+ return new SearchCursor(this, query, pos, caseFold)
+ })
+
+ CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
+ var ranges = []
+ var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
+ while (cur.findNext()) {
+ if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
+ ranges.push({anchor: cur.from(), head: cur.to()})
+ }
+ if (ranges.length)
+ this.setSelections(ranges, 0)
+ })
+});
--- /dev/null
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+var htmlConfig = {
+ autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
+ 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
+ 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
+ 'track': true, 'wbr': true, 'menuitem': true},
+ implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
+ 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
+ 'th': true, 'tr': true},
+ contextGrabbers: {
+ 'dd': {'dd': true, 'dt': true},
+ 'dt': {'dd': true, 'dt': true},
+ 'li': {'li': true},
+ 'option': {'option': true, 'optgroup': true},
+ 'optgroup': {'optgroup': true},
+ 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
+ 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
+ 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
+ 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
+ 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
+ 'rp': {'rp': true, 'rt': true},
+ 'rt': {'rp': true, 'rt': true},
+ 'tbody': {'tbody': true, 'tfoot': true},
+ 'td': {'td': true, 'th': true},
+ 'tfoot': {'tbody': true},
+ 'th': {'td': true, 'th': true},
+ 'thead': {'tbody': true, 'tfoot': true},
+ 'tr': {'tr': true}
+ },
+ doNotIndent: {"pre": true},
+ allowUnquoted: true,
+ allowMissing: true,
+ caseFold: true
+}
+
+var xmlConfig = {
+ autoSelfClosers: {},
+ implicitlyClosed: {},
+ contextGrabbers: {},
+ doNotIndent: {},
+ allowUnquoted: false,
+ allowMissing: false,
+ allowMissingTagName: false,
+ caseFold: false
+}
+
+CodeMirror.defineMode("xml", function(editorConf, config_) {
+ var indentUnit = editorConf.indentUnit
+ var config = {}
+ var defaults = config_.htmlMode ? htmlConfig : xmlConfig
+ for (var prop in defaults) config[prop] = defaults[prop]
+ for (var prop in config_) config[prop] = config_[prop]
+
+ // Return variables for tokenizers
+ var type, setStyle;
+
+ 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";
+ state.tokenize = inTag;
+ return "tag bracket";
+ }
+ } 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;
+ }
+ }
+ inText.isInText = true;
+
+ function inTag(stream, state) {
+ var ch = stream.next();
+ if (ch == ">" || (ch == "/" && stream.eat(">"))) {
+ state.tokenize = inText;
+ type = ch == ">" ? "endTag" : "selfcloseTag";
+ return "tag bracket";
+ } else if (ch == "=") {
+ type = "equals";
+ return null;
+ } else if (ch == "<") {
+ state.tokenize = inText;
+ state.state = baseState;
+ state.tagName = state.tagStart = null;
+ var next = state.tokenize(stream, state);
+ return next ? next + " tag error" : "tag error";
+ } else if (/[\'\"]/.test(ch)) {
+ state.tokenize = inAttribute(ch);
+ state.stringStartCol = stream.column();
+ return state.tokenize(stream, state);
+ } else {
+ stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
+ return "word";
+ }
+ }
+
+ function inAttribute(quote) {
+ var closure = function(stream, state) {
+ while (!stream.eol()) {
+ if (stream.next() == quote) {
+ state.tokenize = inTag;
+ break;
+ }
+ }
+ return "string";
+ };
+ closure.isInAttribute = true;
+ return closure;
+ }
+
+ 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";
+ };
+ }
+
+ function Context(state, tagName, startOfLine) {
+ this.prev = state.context;
+ this.tagName = tagName;
+ this.indent = state.indented;
+ this.startOfLine = startOfLine;
+ if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
+ this.noIndent = true;
+ }
+ function popContext(state) {
+ if (state.context) state.context = state.context.prev;
+ }
+ function maybePopContext(state, nextTagName) {
+ var parentTagName;
+ while (true) {
+ if (!state.context) {
+ return;
+ }
+ parentTagName = state.context.tagName;
+ if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
+ !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
+ return;
+ }
+ popContext(state);
+ }
+ }
+
+ function baseState(type, stream, state) {
+ if (type == "openTag") {
+ state.tagStart = stream.column();
+ return tagNameState;
+ } else if (type == "closeTag") {
+ return closeTagNameState;
+ } else {
+ return baseState;
+ }
+ }
+ function tagNameState(type, stream, state) {
+ if (type == "word") {
+ state.tagName = stream.current();
+ setStyle = "tag";
+ return attrState;
+ } else if (config.allowMissingTagName && type == "endTag") {
+ setStyle = "tag bracket";
+ return attrState(type, stream, state);
+ } else {
+ setStyle = "error";
+ return tagNameState;
+ }
+ }
+ function closeTagNameState(type, stream, state) {
+ if (type == "word") {
+ var tagName = stream.current();
+ if (state.context && state.context.tagName != tagName &&
+ config.implicitlyClosed.hasOwnProperty(state.context.tagName))
+ popContext(state);
+ if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
+ setStyle = "tag";
+ return closeState;
+ } else {
+ setStyle = "tag error";
+ return closeStateErr;
+ }
+ } else if (config.allowMissingTagName && type == "endTag") {
+ setStyle = "tag bracket";
+ return closeState(type, stream, state);
+ } else {
+ setStyle = "error";
+ return closeStateErr;
+ }
+ }
+
+ function closeState(type, _stream, state) {
+ if (type != "endTag") {
+ setStyle = "error";
+ return closeState;
+ }
+ popContext(state);
+ return baseState;
+ }
+ function closeStateErr(type, stream, state) {
+ setStyle = "error";
+ return closeState(type, stream, state);
+ }
+
+ function attrState(type, _stream, state) {
+ if (type == "word") {
+ setStyle = "attribute";
+ return attrEqState;
+ } else if (type == "endTag" || type == "selfcloseTag") {
+ var tagName = state.tagName, tagStart = state.tagStart;
+ state.tagName = state.tagStart = null;
+ if (type == "selfcloseTag" ||
+ config.autoSelfClosers.hasOwnProperty(tagName)) {
+ maybePopContext(state, tagName);
+ } else {
+ maybePopContext(state, tagName);
+ state.context = new Context(state, tagName, tagStart == state.indented);
+ }
+ return baseState;
+ }
+ setStyle = "error";
+ return attrState;
+ }
+ function attrEqState(type, stream, state) {
+ if (type == "equals") return attrValueState;
+ if (!config.allowMissing) setStyle = "error";
+ return attrState(type, stream, state);
+ }
+ function attrValueState(type, stream, state) {
+ if (type == "string") return attrContinuedState;
+ if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
+ setStyle = "error";
+ return attrState(type, stream, state);
+ }
+ function attrContinuedState(type, stream, state) {
+ if (type == "string") return attrContinuedState;
+ return attrState(type, stream, state);
+ }
+
+ return {
+ startState: function(baseIndent) {
+ var state = {tokenize: inText,
+ state: baseState,
+ indented: baseIndent || 0,
+ tagName: null, tagStart: null,
+ context: null}
+ if (baseIndent != null) state.baseIndent = baseIndent
+ return state
+ },
+
+ token: function(stream, state) {
+ if (!state.tagName && stream.sol())
+ state.indented = stream.indentation();
+
+ if (stream.eatSpace()) return null;
+ type = null;
+ var style = state.tokenize(stream, state);
+ if ((style || type) && style != "comment") {
+ setStyle = null;
+ state.state = state.state(type || style, stream, state);
+ if (setStyle)
+ style = setStyle == "error" ? style + " error" : setStyle;
+ }
+ return style;
+ },
+
+ indent: function(state, textAfter, fullLine) {
+ var context = state.context;
+ // Indent multi-line strings (e.g. css).
+ if (state.tokenize.isInAttribute) {
+ if (state.tagStart == state.indented)
+ return state.stringStartCol + 1;
+ else
+ return state.indented + indentUnit;
+ }
+ if (context && context.noIndent) return CodeMirror.Pass;
+ if (state.tokenize != inTag && state.tokenize != inText)
+ return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
+ // Indent the starts of attribute names.
+ if (state.tagName) {
+ if (config.multilineTagIndentPastTag !== false)
+ return state.tagStart + state.tagName.length + 2;
+ else
+ return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
+ }
+ if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
+ var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
+ if (tagAfter && tagAfter[1]) { // Closing tag spotted
+ while (context) {
+ if (context.tagName == tagAfter[2]) {
+ context = context.prev;
+ break;
+ } else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
+ context = context.prev;
+ } else {
+ break;
+ }
+ }
+ } else if (tagAfter) { // Opening tag spotted
+ while (context) {
+ var grabbers = config.contextGrabbers[context.tagName];
+ if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
+ context = context.prev;
+ else
+ break;
+ }
+ }
+ while (context && context.prev && !context.startOfLine)
+ context = context.prev;
+ if (context) return context.indent + indentUnit;
+ else return state.baseIndent || 0;
+ },
+
+ electricInput: /<\/[\s\w:]+>$/,
+ blockCommentStart: "<!--",
+ blockCommentEnd: "-->",
+
+ configuration: config.htmlMode ? "html" : "xml",
+ helperType: config.htmlMode ? "html" : "xml",
+
+ skipAttribute: function(state) {
+ if (state.state == attrValueState)
+ state.state = attrState
+ },
+
+ xmlCurrentTag: function(state) {
+ return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null
+ },
+
+ xmlCurrentContext: function(state) {
+ var context = []
+ for (var cx = state.context; cx; cx = cx.prev)
+ if (cx.tagName) context.push(cx.tagName)
+ return context.reverse()
+ }
+ };
+});
+
+CodeMirror.defineMIME("text/xml", "xml");
+CodeMirror.defineMIME("application/xml", "xml");
+if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
+ CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
+
+});
+++ /dev/null
-/*! jQuery v1.7.2 jquery.com | jquery.org/license */
-(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):b_(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&f.type(b)==="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bZ(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bZ(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bZ(a,c,d,e,"*",g));return l}function bY(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?1:0,g=4;if(d>0){if(c!=="border")for(;e<g;e+=2)c||(d-=parseFloat(f.css(a,"padding"+bx[e]))||0),c==="margin"?d+=parseFloat(f.css(a,c+bx[e]))||0:d-=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0;return d+"px"}d=by(a,b);if(d<0||d==null)d=a.style[b];if(bt.test(d))return d;d=parseFloat(d)||0;if(c)for(;e<g;e+=2)d+=parseFloat(f.css(a,"padding"+bx[e]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+bx[e]))||0);return d+"px"}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;b.nodeType===1&&(b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?b.outerHTML=a.outerHTML:c!=="input"||a.type!=="checkbox"&&a.type!=="radio"?c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text):(a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value)),b.removeAttribute(f.expando),b.removeAttribute("_submit_attached"),b.removeAttribute("_change_attached"))}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c,i[c][d])}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h,i){var j,k=d==null,l=0,m=a.length;if(d&&typeof d=="object"){for(l in d)e.access(a,c,l,d[l],1,h,f);g=1}else if(f!==b){j=i===b&&e.isFunction(f),k&&(j?(j=c,c=function(a,b,c){return j.call(e(a),c)}):(c.call(a,f),c=null));if(c)for(;l<m;l++)c(a[l],d,j?f.call(a[l],l,c(a[l],d)):f,i);g=1}return g?a:k?c.call(a):m?c(a[0],d):h},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m,n=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?n(g):h==="function"&&(!a.unique||!p.has(g))&&c.push(g)},o=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,j=!0,m=k||0,k=0,l=c.length;for(;c&&m<l;m++)if(c[m].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}j=!1,c&&(a.once?e===!0?p.disable():c=[]:d&&d.length&&(e=d.shift(),p.fireWith(e[0],e[1])))},p={add:function(){if(c){var a=c.length;n(arguments),j?l=c.length:e&&e!==!0&&(k=a,o(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){j&&f<=l&&(l--,f<=m&&m--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&p.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(j?a.once||d.push([b,c]):(!a.once||!e)&&o(b,c));return this},fire:function(){p.fireWith(this,arguments);return this},fired:function(){return!!i}};return p};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p=c.createElement("div"),q=c.documentElement;p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="<div "+n+"display:block;'><div style='"+t+"0;display:block;overflow:hidden;'></div></div>"+"<table "+n+"' cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="<table><tr><td style='"+t+"0;display:none'></td><td>t</td></tr></table>",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="<div style='width:5px;'></div>",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h,i,j=this[0],k=0,m=null;if(a===b){if(this.length){m=f.data(j);if(j.nodeType===1&&!f._data(j,"parsedAttrs")){g=j.attributes;for(i=g.length;k<i;k++)h=g[k].name,h.indexOf("data-")===0&&(h=f.camelCase(h.substring(5)),l(j,h,m[h]));f._data(j,"parsedAttrs",!0)}}return m}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!";return f.access(this,function(c){if(c===b){m=this.triggerHandler("getData"+e,[d[0]]),m===b&&j&&(m=f.data(j,a),m=l(j,a,m));return m===b&&d[1]?this.data(d[0]):m}d[1]=c,this.each(function(){var b=f(this);b.triggerHandler("setData"+e,d),f.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length<d)return f.queue(this[0],a);return c===b?this:this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise(c)}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,f.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i<g;i++)e=d[i],e&&(c=f.propFix[e]||e,h=u.test(e),h||f.attr(a,e,""),a.removeAttribute(v?e:c),h&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0,coords:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(
-a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:g&&G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||i.preDispatch.call(this,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(m=c.target;m!=this;m=m.parentNode||this)if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;k<e;k++)s=d[k],t=s.selector,p[t]===b&&(p[t]=s.quick?H(m,s.quick):n.is(t)),p[t]&&r.push(s);r.length&&j.push({elem:m,matches:r})}}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){q=j[k],c.currentTarget=q.elem;for(l=0;l<q.matches.length&&!c.isImmediatePropagationStopped();l++){s=q.matches[l];if(h||!c.namespace&&!s.namespace||c.namespace_re&&c.namespace_re.test(s.namespace))c.data=s.data,c.handleObj=s,o=((f.event.special[s.origType]||{}).handle||s.handler).apply(q.elem,g),o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()))}}i.postDispatch&&i.postDispatch.call(this,c);return c.result}},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),d._submit_attached=!0)})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9||d===11){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.globalPOS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")[\\s/>]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f
-.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(f.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(g){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,function(a,b){b.src?f.ajax({type:"GET",global:!1,url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1></$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]==="<table>"&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i<u;i++)bn(l[i]);else bn(l);l.nodeType?j.push(l):j=f.merge(j,l)}if(d){g=function(a){return!a.type||be.test(a.type)};for(k=0;j[k];k++){h=j[k];if(e&&f.nodeName(h,"script")&&(!h.type||be.test(h.type)))e.push(h.parentNode?h.parentNode.removeChild(h):h);else{if(h.nodeType===1){var v=f.grep(h.getElementsByTagName("script"),g);j.splice.apply(j,[k+1,0].concat(v))}d.appendChild(h)}}}return j},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bp=/alpha\([^)]*\)/i,bq=/opacity=([^)]*)/,br=/([A-Z]|^ms)/g,bs=/^[\-+]?(?:\d*\.)?\d+$/i,bt=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,bu=/^([\-+])=([\-+.\de]+)/,bv=/^margin/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Top","Right","Bottom","Left"],by,bz,bA;f.fn.css=function(a,c){return f.access(this,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)},a,c,arguments.length>1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),(e===""&&f.css(d,"display")==="none"||!f.contains(d.ownerDocument.documentElement,d))&&f._data(d,"olddisplay",cu(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(ct("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(ct("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o,p,q;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]);if((k=f.cssHooks[g])&&"expand"in k){l=k.expand(a[g]),delete a[g];for(i in l)i in a||(a[i]=l[i])}}for(g in a){h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cu(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cm.test(h)?(q=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),q?(f._data(this,"toggle"+i,q==="show"?"hide":"show"),j[q]()):j[h]()):(m=cn.exec(h),n=j.cur(),m?(o=parseFloat(m[2]),p=m[3]||(f.cssNumber[i]?"":"px"),p!=="px"&&(f.style(this,i,(o||1)+p),n=(o||1)/j.cur()*n,f.style(this,i,n+p)),m[1]&&(o=(m[1]==="-="?-1:1)*o+n),j.custom(n,o,p)):j.custom(n,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:ct("show",1),slideUp:ct("hide",1),slideToggle:ct("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a){return a},swing:function(a){return-Math.cos(a*Math.PI)/2+.5}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cq||cr(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){f._data(e.elem,"fxshow"+e.prop)===b&&(e.options.hide?f._data(e.elem,"fxshow"+e.prop,e.start):e.options.show&&f._data(e.elem,"fxshow"+e.prop,e.end))},h()&&f.timers.push(h)&&!co&&(co=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cq||cr(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(cp.concat.apply([],cp),function(a,b){b.indexOf("margin")&&(f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)})}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cv,cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?cv=function(a,b,c,d){try{d=a.getBoundingClientRect()}catch(e){}if(!d||!f.contains(c,a))return d?{top:d.top,left:d.left}:{top:0,left:0};var g=b.body,h=cy(b),i=c.clientTop||g.clientTop||0,j=c.clientLeft||g.clientLeft||0,k=h.pageYOffset||f.support.boxModel&&c.scrollTop||g.scrollTop,l=h.pageXOffset||f.support.boxModel&&c.scrollLeft||g.scrollLeft,m=d.top+k-i,n=d.left+l-j;return{top:m,left:n}}:cv=function(a,b,c){var d,e=a.offsetParent,g=a,h=b.body,i=b.defaultView,j=i?i.getComputedStyle(a,null):a.currentStyle,k=a.offsetTop,l=a.offsetLeft;while((a=a.parentNode)&&a!==h&&a!==c){if(f.support.fixedPosition&&j.position==="fixed")break;d=i?i.getComputedStyle(a,null):a.currentStyle,k-=a.scrollTop,l-=a.scrollLeft,a===e&&(k+=a.offsetTop,l+=a.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(a.nodeName))&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),g=e,e=a.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),j=d}if(j.position==="relative"||j.position==="static")k+=h.offsetTop,l+=h.offsetLeft;f.support.fixedPosition&&j.position==="fixed"&&(k+=Math.max(c.scrollTop,h.scrollTop),l+=Math.max(c.scrollLeft,h.scrollLeft));return{top:k,left:l}},f.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){f.offset.setOffset(this,a,b)});var c=this[0],d=c&&c.ownerDocument;if(!d)return null;if(c===d.body)return f.offset.bodyOffset(c);return cv(c,d,d.documentElement)},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file
+++ /dev/null
-/*
- * jQuery Autocomplete plugin 1.1
- *
- * Copyright (c) 2009 Jörn Zaefferer
- *
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- *
- * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
- *
- * changed by Radek Czajka (FNP), 20100907: ignore alt keydown
- */
-
-;(function($) {
-
-$.fn.extend({
- autocomplete: function(urlOrData, options) {
- var isUrl = typeof urlOrData == "string";
- options = $.extend({}, $.Autocompleter.defaults, {
- url: isUrl ? urlOrData : null,
- data: isUrl ? null : urlOrData,
- delay: isUrl ? $.Autocompleter.defaults.delay : 10,
- max: options && !options.scroll ? 10 : 150
- }, options);
-
- // if highlight is set to false, replace it with a do-nothing function
- options.highlight = options.highlight || function(value) { return value; };
-
- // if the formatMatch option is not specified, then use formatItem for backwards compatibility
- options.formatMatch = options.formatMatch || options.formatItem;
-
- return this.each(function() {
- new $.Autocompleter(this, options);
- });
- },
- result: function(handler) {
- return this.bind("result", handler);
- },
- search: function(handler) {
- return this.trigger("search", [handler]);
- },
- flushCache: function() {
- return this.trigger("flushCache");
- },
- setOptions: function(options){
- return this.trigger("setOptions", [options]);
- },
- unautocomplete: function() {
- return this.trigger("unautocomplete");
- }
-});
-
-$.Autocompleter = function(input, options) {
-
- var KEY = {
- UP: 38,
- DOWN: 40,
- DEL: 46,
- TAB: 9,
- RETURN: 13,
- ESC: 27,
- COMMA: 188,
- PAGEUP: 33,
- PAGEDOWN: 34,
- BACKSPACE: 8
- };
-
- // Create $ object for input element
- var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
-
- var timeout;
- var previousValue = "";
- var cache = $.Autocompleter.Cache(options);
- var hasFocus = 0;
- var lastKeyPressCode;
- var config = {
- mouseDownOnSelect: false
- };
- var select = $.Autocompleter.Select(options, input, selectCurrent, config);
-
- var blockSubmit;
-
- // prevent form submit in opera when selecting with return key
- $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
- if (blockSubmit) {
- blockSubmit = false;
- return false;
- }
- });
-
- // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
- $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
- // a keypress means the input has focus
- // avoids issue where input had focus before the autocomplete was applied
- hasFocus = 1;
- // track last key pressed
- lastKeyPressCode = event.keyCode;
- switch(event.keyCode) {
-
- // changed by fnp
- case 18:
- case 17:
- break;
- // changed by fnp
-
- case KEY.UP:
- event.preventDefault();
- if ( select.visible() ) {
- select.prev();
- } else {
- onChange(0, true);
- }
- break;
-
- case KEY.DOWN:
- event.preventDefault();
- if ( select.visible() ) {
- select.next();
- } else {
- onChange(0, true);
- }
- break;
-
- case KEY.PAGEUP:
- event.preventDefault();
- if ( select.visible() ) {
- select.pageUp();
- } else {
- onChange(0, true);
- }
- break;
-
- case KEY.PAGEDOWN:
- event.preventDefault();
- if ( select.visible() ) {
- select.pageDown();
- } else {
- onChange(0, true);
- }
- break;
-
- // matches also semicolon
- case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
- case KEY.TAB:
- case KEY.RETURN:
- if( selectCurrent() ) {
- // stop default to prevent a form submit, Opera needs special handling
- event.preventDefault();
- blockSubmit = true;
- return false;
- }
- break;
-
- case KEY.ESC:
- select.hide();
- break;
-
- default:
- clearTimeout(timeout);
- timeout = setTimeout(onChange, options.delay);
- break;
- }
- }).focus(function(){
- // track whether the field has focus, we shouldn't process any
- // results if the field no longer has focus
- hasFocus++;
- }).blur(function() {
- hasFocus = 0;
- if (!config.mouseDownOnSelect) {
- hideResults();
- }
- }).click(function() {
- // show select when clicking in a focused field
- if ( hasFocus++ > 1 && !select.visible() ) {
- onChange(0, true);
- }
- }).bind("search", function() {
- // TODO why not just specifying both arguments?
- var fn = (arguments.length > 1) ? arguments[1] : null;
- function findValueCallback(q, data) {
- var result;
- if( data && data.length ) {
- for (var i=0; i < data.length; i++) {
- if( data[i].result.toLowerCase() == q.toLowerCase() ) {
- result = data[i];
- break;
- }
- }
- }
- if( typeof fn == "function" ) fn(result);
- else $input.trigger("result", result && [result.data, result.value]);
- }
- $.each(trimWords($input.val()), function(i, value) {
- request(value, findValueCallback, findValueCallback);
- });
- }).bind("flushCache", function() {
- cache.flush();
- }).bind("setOptions", function() {
- $.extend(options, arguments[1]);
- // if we've updated the data, repopulate
- if ( "data" in arguments[1] )
- cache.populate();
- }).bind("unautocomplete", function() {
- select.unbind();
- $input.unbind();
- $(input.form).unbind(".autocomplete");
- });
-
-
- function selectCurrent() {
- var selected = select.selected();
- if( !selected )
- return false;
-
- var v = selected.result;
- previousValue = v;
-
- if ( options.multiple ) {
- var words = trimWords($input.val());
- if ( words.length > 1 ) {
- var seperator = options.multipleSeparator.length;
- var cursorAt = $(input).selection().start;
- var wordAt, progress = 0;
- $.each(words, function(i, word) {
- progress += word.length;
- if (cursorAt <= progress) {
- wordAt = i;
- return false;
- }
- progress += seperator;
- });
- words[wordAt] = v;
- // TODO this should set the cursor to the right position, but it gets overriden somewhere
- //$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
- v = words.join( options.multipleSeparator );
- }
- v += options.multipleSeparator;
- }
-
- $input.val(v);
- hideResultsNow();
- $input.trigger("result", [selected.data, selected.value]);
- return true;
- }
-
- function onChange(crap, skipPrevCheck) {
- if( lastKeyPressCode == KEY.DEL ) {
- select.hide();
- return;
- }
-
- var currentValue = $input.val();
-
- if ( !skipPrevCheck && currentValue == previousValue )
- return;
-
- previousValue = currentValue;
-
- currentValue = lastWord(currentValue);
- if ( currentValue.length >= options.minChars) {
- $input.addClass(options.loadingClass);
- if (!options.matchCase)
- currentValue = currentValue.toLowerCase();
- request(currentValue, receiveData, hideResultsNow);
- } else {
- stopLoading();
- select.hide();
- }
- };
-
- function trimWords(value) {
- if (!value)
- return [""];
- if (!options.multiple)
- return [$.trim(value)];
- return $.map(value.split(options.multipleSeparator), function(word) {
- return $.trim(value).length ? $.trim(word) : null;
- });
- }
-
- function lastWord(value) {
- if ( !options.multiple )
- return value;
- var words = trimWords(value);
- if (words.length == 1)
- return words[0];
- var cursorAt = $(input).selection().start;
- if (cursorAt == value.length) {
- words = trimWords(value)
- } else {
- words = trimWords(value.replace(value.substring(cursorAt), ""));
- }
- return words[words.length - 1];
- }
-
- // fills in the input box w/the first match (assumed to be the best match)
- // q: the term entered
- // sValue: the first matching result
- function autoFill(q, sValue){
- // autofill in the complete box w/the first match as long as the user hasn't entered in more data
- // if the last user key pressed was backspace, don't autofill
- if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
- // fill in the value (keep the case the user has typed)
- $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
- // select the portion of the value not typed by the user (so the next character will erase)
- $(input).selection(previousValue.length, previousValue.length + sValue.length);
- }
- };
-
- function hideResults() {
- clearTimeout(timeout);
- timeout = setTimeout(hideResultsNow, 200);
- };
-
- function hideResultsNow() {
- var wasVisible = select.visible();
- select.hide();
- clearTimeout(timeout);
- stopLoading();
- if (options.mustMatch) {
- // call search and run callback
- $input.search(
- function (result){
- // if no value found, clear the input box
- if( !result ) {
- if (options.multiple) {
- var words = trimWords($input.val()).slice(0, -1);
- $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
- }
- else {
- $input.val( "" );
- $input.trigger("result", null);
- }
- }
- }
- );
- }
- };
-
- function receiveData(q, data) {
- if ( data && data.length && hasFocus ) {
- stopLoading();
- select.display(data, q);
- autoFill(q, data[0].value);
- select.show();
- } else {
- hideResultsNow();
- }
- };
-
- function request(term, success, failure) {
- if (!options.matchCase)
- term = term.toLowerCase();
- var data = cache.load(term);
- // recieve the cached data
- if (data && data.length) {
- success(term, data);
- // if an AJAX url has been supplied, try loading the data now
- } else if( (typeof options.url == "string") && (options.url.length > 0) ){
-
- var extraParams = {
- timestamp: +new Date()
- };
- $.each(options.extraParams, function(key, param) {
- extraParams[key] = typeof param == "function" ? param() : param;
- });
-
- $.ajax({
- // try to leverage ajaxQueue plugin to abort previous requests
- mode: "abort",
- // limit abortion to this input
- port: "autocomplete" + input.name,
- dataType: options.dataType,
- url: options.url,
- data: $.extend({
- q: lastWord(term),
- limit: options.max
- }, extraParams),
- success: function(data) {
- var parsed = options.parse && options.parse(data) || parse(data);
- cache.add(term, parsed);
- success(term, parsed);
- }
- });
- } else {
- // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
- select.emptyList();
- failure(term);
- }
- };
-
- function parse(data) {
- var parsed = [];
- var rows = data.split("\n");
- for (var i=0; i < rows.length; i++) {
- var row = $.trim(rows[i]);
- if (row) {
- row = row.split("|");
- parsed[parsed.length] = {
- data: row,
- value: row[0],
- result: options.formatResult && options.formatResult(row, row[0]) || row[0]
- };
- }
- }
- return parsed;
- };
-
- function stopLoading() {
- $input.removeClass(options.loadingClass);
- };
-
-};
-
-$.Autocompleter.defaults = {
- inputClass: "ac_input",
- resultsClass: "ac_results",
- loadingClass: "ac_loading",
- minChars: 1,
- delay: 400,
- matchCase: false,
- matchSubset: true,
- matchContains: false,
- cacheLength: 10,
- max: 100,
- mustMatch: false,
- extraParams: {},
- selectFirst: true,
- formatItem: function(row) { return row[0]; },
- formatMatch: null,
- autoFill: false,
- width: 0,
- multiple: false,
- multipleSeparator: ", ",
- highlight: function(value, term) {
- return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
- },
- scroll: true,
- scrollHeight: 180
-};
-
-$.Autocompleter.Cache = function(options) {
-
- var data = {};
- var length = 0;
-
- function matchSubset(s, sub) {
- if (!options.matchCase)
- s = s.toLowerCase();
- var i = s.indexOf(sub);
- if (options.matchContains == "word"){
- i = s.toLowerCase().search("\\b" + sub.toLowerCase());
- }
- if (i == -1) return false;
- return i == 0 || options.matchContains;
- };
-
- function add(q, value) {
- if (length > options.cacheLength){
- flush();
- }
- if (!data[q]){
- length++;
- }
- data[q] = value;
- }
-
- function populate(){
- if( !options.data ) return false;
- // track the matches
- var stMatchSets = {},
- nullData = 0;
-
- // no url was specified, we need to adjust the cache length to make sure it fits the local data store
- if( !options.url ) options.cacheLength = 1;
-
- // track all options for minChars = 0
- stMatchSets[""] = [];
-
- // loop through the array and create a lookup structure
- for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
- var rawValue = options.data[i];
- // if rawValue is a string, make an array otherwise just reference the array
- rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
-
- var value = options.formatMatch(rawValue, i+1, options.data.length);
- if ( value === false )
- continue;
-
- var firstChar = value.charAt(0).toLowerCase();
- // if no lookup array for this character exists, look it up now
- if( !stMatchSets[firstChar] )
- stMatchSets[firstChar] = [];
-
- // if the match is a string
- var row = {
- value: value,
- data: rawValue,
- result: options.formatResult && options.formatResult(rawValue) || value
- };
-
- // push the current match into the set list
- stMatchSets[firstChar].push(row);
-
- // keep track of minChars zero items
- if ( nullData++ < options.max ) {
- stMatchSets[""].push(row);
- }
- };
-
- // add the data items to the cache
- $.each(stMatchSets, function(i, value) {
- // increase the cache size
- options.cacheLength++;
- // add to the cache
- add(i, value);
- });
- }
-
- // populate any existing data
- setTimeout(populate, 25);
-
- function flush(){
- data = {};
- length = 0;
- }
-
- return {
- flush: flush,
- add: add,
- populate: populate,
- load: function(q) {
- if (!options.cacheLength || !length)
- return null;
- /*
- * if dealing w/local data and matchContains than we must make sure
- * to loop through all the data collections looking for matches
- */
- if( !options.url && options.matchContains ){
- // track all matches
- var csub = [];
- // loop through all the data grids for matches
- for( var k in data ){
- // don't search through the stMatchSets[""] (minChars: 0) cache
- // this prevents duplicates
- if( k.length > 0 ){
- var c = data[k];
- $.each(c, function(i, x) {
- // if we've got a match, add it to the array
- if (matchSubset(x.value, q)) {
- csub.push(x);
- }
- });
- }
- }
- return csub;
- } else
- // if the exact item exists, use it
- if (data[q]){
- return data[q];
- } else
- if (options.matchSubset) {
- for (var i = q.length - 1; i >= options.minChars; i--) {
- var c = data[q.substr(0, i)];
- if (c) {
- var csub = [];
- $.each(c, function(i, x) {
- if (matchSubset(x.value, q)) {
- csub[csub.length] = x;
- }
- });
- return csub;
- }
- }
- }
- return null;
- }
- };
-};
-
-$.Autocompleter.Select = function (options, input, select, config) {
- var CLASSES = {
- ACTIVE: "ac_over"
- };
-
- var listItems,
- active = -1,
- data,
- term = "",
- needsInit = true,
- element,
- list;
-
- // Create results
- function init() {
- if (!needsInit)
- return;
- element = $("<div/>")
- .hide()
- .addClass(options.resultsClass)
- .css("position", "absolute")
- .appendTo(document.body);
-
- list = $("<ul/>").appendTo(element).mouseover( function(event) {
- if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
- active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
- $(target(event)).addClass(CLASSES.ACTIVE);
- }
- }).click(function(event) {
- $(target(event)).addClass(CLASSES.ACTIVE);
- select();
- // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
- input.focus();
- return false;
- }).mousedown(function() {
- config.mouseDownOnSelect = true;
- }).mouseup(function() {
- config.mouseDownOnSelect = false;
- });
-
- if( options.width > 0 )
- element.css("width", options.width);
-
- needsInit = false;
- }
-
- function target(event) {
- var element = event.target;
- while(element && element.tagName != "LI")
- element = element.parentNode;
- // more fun with IE, sometimes event.target is empty, just ignore it then
- if(!element)
- return [];
- return element;
- }
-
- function moveSelect(step) {
- listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
- movePosition(step);
- var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
- if(options.scroll) {
- var offset = 0;
- listItems.slice(0, active).each(function() {
- offset += this.offsetHeight;
- });
- if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
- list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
- } else if(offset < list.scrollTop()) {
- list.scrollTop(offset);
- }
- }
- };
-
- function movePosition(step) {
- active += step;
- if (active < 0) {
- active = listItems.size() - 1;
- } else if (active >= listItems.size()) {
- active = 0;
- }
- }
-
- function limitNumberOfItems(available) {
- return options.max && options.max < available
- ? options.max
- : available;
- }
-
- function fillList() {
- list.empty();
- var max = limitNumberOfItems(data.length);
- for (var i=0; i < max; i++) {
- if (!data[i])
- continue;
- var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
- if ( formatted === false )
- continue;
- var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
- $.data(li, "ac_data", data[i]);
- }
- listItems = list.find("li");
- if ( options.selectFirst ) {
- listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
- active = 0;
- }
- // apply bgiframe if available
- if ( $.fn.bgiframe )
- list.bgiframe();
- }
-
- return {
- display: function(d, q) {
- init();
- data = d;
- term = q;
- fillList();
- },
- next: function() {
- moveSelect(1);
- },
- prev: function() {
- moveSelect(-1);
- },
- pageUp: function() {
- if (active != 0 && active - 8 < 0) {
- moveSelect( -active );
- } else {
- moveSelect(-8);
- }
- },
- pageDown: function() {
- if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
- moveSelect( listItems.size() - 1 - active );
- } else {
- moveSelect(8);
- }
- },
- hide: function() {
- element && element.hide();
- listItems && listItems.removeClass(CLASSES.ACTIVE);
- active = -1;
- },
- visible : function() {
- return element && element.is(":visible");
- },
- current: function() {
- return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
- },
- show: function() {
- var offset = $(input).offset();
- element.css({
- width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
- top: offset.top + input.offsetHeight,
- left: offset.left
- }).show();
- if(options.scroll) {
- list.scrollTop(0);
- list.css({
- maxHeight: options.scrollHeight,
- overflow: 'auto'
- });
-
- if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
- var listHeight = 0;
- listItems.each(function() {
- listHeight += this.offsetHeight;
- });
- var scrollbarsVisible = listHeight > options.scrollHeight;
- list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
- if (!scrollbarsVisible) {
- // IE doesn't recalculate width when scrollbar disappears
- listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
- }
- }
-
- }
- },
- selected: function() {
- var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
- return selected && selected.length && $.data(selected[0], "ac_data");
- },
- emptyList: function (){
- list && list.empty();
- },
- unbind: function() {
- element && element.remove();
- }
- };
-};
-
-$.fn.selection = function(start, end) {
- if (start !== undefined) {
- return this.each(function() {
- if( this.createTextRange ){
- var selRange = this.createTextRange();
- if (end === undefined || start == end) {
- selRange.move("character", start);
- selRange.select();
- } else {
- selRange.collapse(true);
- selRange.moveStart("character", start);
- selRange.moveEnd("character", end);
- selRange.select();
- }
- } else if( this.setSelectionRange ){
- this.setSelectionRange(start, end);
- } else if( this.selectionStart ){
- this.selectionStart = start;
- this.selectionEnd = end;
- }
- });
- }
- var field = this[0];
- if ( field.createTextRange ) {
- var range = document.selection.createRange(),
- orig = field.value,
- teststring = "<->",
- textLength = range.text.length;
- range.text = teststring;
- var caretAt = field.value.indexOf(teststring);
- field.value = orig;
- this.selection(caretAt, caretAt + textLength);
- return {
- start: caretAt,
- end: caretAt + textLength
- }
- } else if( field.selectionStart !== undefined ){
- return {
- start: field.selectionStart,
- end: field.selectionEnd
- }
- }
-};
-
-})(jQuery);
\ No newline at end of file
-/*!
- * jQuery blockUI plugin
- * Version 2.31 (06-JAN-2010)
- * @requires jQuery v1.2.3 or later
- *
- * Examples at: http://malsup.com/jquery/block/
- * Copyright (c) 2007-2008 M. Alsup
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- *
- * Thanks to Amir-Hossein Sobhi for some excellent contributions!
- */
-
-;(function($) {
-
-if (/1\.(0|1|2)\.(0|1|2)/.test($.fn.jquery) || /^1.1/.test($.fn.jquery)) {
- alert('blockUI requires jQuery v1.2.3 or later! You are using v' + $.fn.jquery);
- return;
-}
-
-$.fn._fadeIn = $.fn.fadeIn;
-
-var noOp = function() {};
-
-// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
-// retarded userAgent strings on Vista)
-var mode = document.documentMode || 0;
-var setExpr = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8);
-var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent) && !mode;
-
-// global $ methods for blocking/unblocking the entire page
-$.blockUI = function(opts) { install(window, opts); };
-$.unblockUI = function(opts) { remove(window, opts); };
-
-// convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
-$.growlUI = function(title, message, timeout, onClose) {
- var $m = $('<div class="growlUI"></div>');
- if (title) $m.append('<h1>'+title+'</h1>');
- if (message) $m.append('<h2>'+message+'</h2>');
- if (timeout == undefined) timeout = 3000;
- $.blockUI({
- message: $m, fadeIn: 700, fadeOut: 1000, centerY: false,
- timeout: timeout, showOverlay: false,
- onUnblock: onClose,
- css: $.blockUI.defaults.growlCSS
- });
-};
-
-// plugin method for blocking element content
-$.fn.block = function(opts) {
- return this.unblock({ fadeOut: 0 }).each(function() {
- if ($.css(this,'position') == 'static')
- this.style.position = 'relative';
- if ($.browser.msie)
- this.style.zoom = 1; // force 'hasLayout'
- install(this, opts);
- });
-};
-
-// plugin method for unblocking element content
-$.fn.unblock = function(opts) {
- return this.each(function() {
- remove(this, opts);
- });
-};
-
-$.blockUI.version = 2.31; // 2nd generation blocking at no extra cost!
-
-// override these in your code to change the default behavior and style
-$.blockUI.defaults = {
- // message displayed when blocking (use null for no message)
- message: '<h1>Please wait...</h1>',
-
- title: null, // title string; only used when theme == true
- draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded)
-
- theme: false, // set to true to use with jQuery UI themes
-
- // styles for the message when blocking; if you wish to disable
- // these and use an external stylesheet then do this in your code:
- // $.blockUI.defaults.css = {};
- css: {
- padding: 0,
- margin: 0,
- width: '30%',
- top: '40%',
- left: '35%',
- textAlign: 'center',
- color: '#000',
- border: '3px solid #aaa',
- backgroundColor:'#fff',
- cursor: 'wait'
- },
-
- // minimal style set used when themes are used
- themedCSS: {
- width: '30%',
- top: '40%',
- left: '35%'
- },
-
- // styles for the overlay
- overlayCSS: {
- backgroundColor: '#000',
- opacity: 0.6,
- cursor: 'wait'
- },
-
- // styles applied when using $.growlUI
- growlCSS: {
- width: '350px',
- top: '10px',
- left: '',
- right: '10px',
- border: 'none',
- padding: '5px',
- opacity: 0.6,
- cursor: 'default',
- color: '#fff',
- backgroundColor: '#000',
- '-webkit-border-radius': '10px',
- '-moz-border-radius': '10px'
- },
-
- // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
- // (hat tip to Jorge H. N. de Vasconcelos)
- iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
-
- // force usage of iframe in non-IE browsers (handy for blocking applets)
- forceIframe: false,
-
- // z-index for the blocking overlay
- baseZ: 1000,
-
- // set these to true to have the message automatically centered
- centerX: true, // <-- only effects element blocking (page block controlled via css above)
- centerY: true,
-
- // allow body element to be stetched in ie6; this makes blocking look better
- // on "short" pages. disable if you wish to prevent changes to the body height
- allowBodyStretch: true,
-
- // enable if you want key and mouse events to be disabled for content that is blocked
- bindEvents: true,
-
- // be default blockUI will supress tab navigation from leaving blocking content
- // (if bindEvents is true)
- constrainTabKey: true,
-
- // fadeIn time in millis; set to 0 to disable fadeIn on block
- fadeIn: 200,
-
- // fadeOut time in millis; set to 0 to disable fadeOut on unblock
- fadeOut: 400,
-
- // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
- timeout: 0,
-
- // disable if you don't want to show the overlay
- showOverlay: true,
-
- // if true, focus will be placed in the first available input field when
- // page blocking
- focusInput: true,
-
- // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
- applyPlatformOpacityRules: true,
-
- // callback method invoked when fadeIn has completed and blocking message is visible
- onBlock: null,
-
- // callback method invoked when unblocking has completed; the callback is
- // passed the element that has been unblocked (which is the window object for page
- // blocks) and the options that were passed to the unblock call:
- // onUnblock(element, options)
- onUnblock: null,
-
- // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
- quirksmodeOffsetHack: 4
-};
-
-// private data and functions follow...
-
-var pageBlock = null;
-var pageBlockEls = [];
-
-function install(el, opts) {
- var full = (el == window);
- var msg = opts && opts.message !== undefined ? opts.message : undefined;
- opts = $.extend({}, $.blockUI.defaults, opts || {});
- opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
- var css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
- var themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
- msg = msg === undefined ? opts.message : msg;
-
- // remove the current block (if there is one)
- if (full && pageBlock)
- remove(window, {fadeOut:0});
-
- // if an existing element is being used as the blocking content then we capture
- // its current place in the DOM (and current display style) so we can restore
- // it when we unblock
- if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
- var node = msg.jquery ? msg[0] : msg;
- var data = {};
- $(el).data('blockUI.history', data);
- data.el = node;
- data.parent = node.parentNode;
- data.display = node.style.display;
- data.position = node.style.position;
- if (data.parent)
- data.parent.removeChild(node);
- }
-
- var z = opts.baseZ;
-
- // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
- // layer1 is the iframe layer which is used to supress bleed through of underlying content
- // layer2 is the overlay layer which has opacity and a wait cursor (by default)
- // layer3 is the message content that is displayed while blocking
-
- var lyr1 = ($.browser.msie || opts.forceIframe)
- ? $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>')
- : $('<div class="blockUI" style="display:none"></div>');
- var lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
-
- var lyr3;
- if (opts.theme && full) {
- var s = '<div class="blockUI blockMsg blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+z+';display:none;position:fixed">' +
- '<div class="ui-widget-header ui-dialog-titlebar blockTitle">'+(opts.title || ' ')+'</div>' +
- '<div class="ui-widget-content ui-dialog-content"></div>' +
- '</div>';
- lyr3 = $(s);
- }
- else {
- lyr3 = full ? $('<div class="blockUI blockMsg blockPage" style="z-index:'+z+';display:none;position:fixed"></div>')
- : $('<div class="blockUI blockMsg blockElement" style="z-index:'+z+';display:none;position:absolute"></div>');
- }
-
- // if we have a message, style it
- if (msg) {
- if (opts.theme) {
- lyr3.css(themedCSS);
- lyr3.addClass('ui-widget-content');
- }
- else
- lyr3.css(css);
- }
-
- // style the overlay
- if (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform)))
- lyr2.css(opts.overlayCSS);
- lyr2.css('position', full ? 'fixed' : 'absolute');
-
- // make iframe layer transparent in IE
- if ($.browser.msie || opts.forceIframe)
- lyr1.css('opacity',0.0);
-
- //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
- var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
- $.each(layers, function() {
- this.appendTo($par);
- });
-
- if (opts.theme && opts.draggable && $.fn.draggable) {
- lyr3.draggable({
- handle: '.ui-dialog-titlebar',
- cancel: 'li'
- });
- }
-
- // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
- var expr = setExpr && (!$.boxModel || $('object,embed', full ? null : el).length > 0);
- if (ie6 || expr) {
- // give body 100% height
- if (full && opts.allowBodyStretch && $.boxModel)
- $('html,body').css('height','100%');
-
- // fix ie6 issue when blocked element has a border width
- if ((ie6 || !$.boxModel) && !full) {
- var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
- var fixT = t ? '(0 - '+t+')' : 0;
- var fixL = l ? '(0 - '+l+')' : 0;
- }
-
- // simulate fixed position
- $.each([lyr1,lyr2,lyr3], function(i,o) {
- var s = o[0].style;
- s.position = 'absolute';
- if (i < 2) {
- full ? s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"')
- : s.setExpression('height','this.parentNode.offsetHeight + "px"');
- full ? s.setExpression('width','jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"')
- : s.setExpression('width','this.parentNode.offsetWidth + "px"');
- if (fixL) s.setExpression('left', fixL);
- if (fixT) s.setExpression('top', fixT);
- }
- else if (opts.centerY) {
- if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
- s.marginTop = 0;
- }
- else if (!opts.centerY && full) {
- var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0;
- var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
- s.setExpression('top',expression);
- }
- });
- }
-
- // show the message
- if (msg) {
- if (opts.theme)
- lyr3.find('.ui-widget-content').append(msg);
- else
- lyr3.append(msg);
- if (msg.jquery || msg.nodeType)
- $(msg).show();
- }
-
- if (($.browser.msie || opts.forceIframe) && opts.showOverlay)
- lyr1.show(); // opacity is zero
- if (opts.fadeIn) {
- var cb = opts.onBlock ? opts.onBlock : noOp;
- var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
- var cb2 = msg ? cb : noOp;
- if (opts.showOverlay)
- lyr2._fadeIn(opts.fadeIn, cb1);
- if (msg)
- lyr3._fadeIn(opts.fadeIn, cb2);
- }
- else {
- if (opts.showOverlay)
- lyr2.show();
- if (msg)
- lyr3.show();
- if (opts.onBlock)
- opts.onBlock();
- }
-
- // bind key and mouse events
- bind(1, el, opts);
-
- if (full) {
- pageBlock = lyr3[0];
- pageBlockEls = $(':input:enabled:visible',pageBlock);
- if (opts.focusInput)
- setTimeout(focus, 20);
- }
- else
- center(lyr3[0], opts.centerX, opts.centerY);
-
- if (opts.timeout) {
- // auto-unblock
- var to = setTimeout(function() {
- full ? $.unblockUI(opts) : $(el).unblock(opts);
- }, opts.timeout);
- $(el).data('blockUI.timeout', to);
- }
-};
-
-// remove the block
-function remove(el, opts) {
- var full = (el == window);
- var $el = $(el);
- var data = $el.data('blockUI.history');
- var to = $el.data('blockUI.timeout');
- if (to) {
- clearTimeout(to);
- $el.removeData('blockUI.timeout');
- }
- opts = $.extend({}, $.blockUI.defaults, opts || {});
- bind(0, el, opts); // unbind events
-
- var els;
- if (full) // crazy selector to handle odd field errors in ie6/7
- els = $('body').children().filter('.blockUI').add('body > .blockUI');
- else
- els = $('.blockUI', el);
-
- if (full)
- pageBlock = pageBlockEls = null;
-
- if (opts.fadeOut) {
- els.fadeOut(opts.fadeOut);
- setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut);
- }
- else
- reset(els, data, opts, el);
-};
-
-// move blocking element back into the DOM where it started
-function reset(els,data,opts,el) {
- els.each(function(i,o) {
- // remove via DOM calls so we don't lose event handlers
- if (this.parentNode)
- this.parentNode.removeChild(this);
- });
-
- if (data && data.el) {
- data.el.style.display = data.display;
- data.el.style.position = data.position;
- if (data.parent)
- data.parent.appendChild(data.el);
- $(el).removeData('blockUI.history');
- }
-
- if (typeof opts.onUnblock == 'function')
- opts.onUnblock(el,opts);
-};
-
-// bind/unbind the handler
-function bind(b, el, opts) {
- var full = el == window, $el = $(el);
-
- // don't bother unbinding if there is nothing to unbind
- if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
- return;
- if (!full)
- $el.data('blockUI.isBlocked', b);
-
- // don't bind events when overlay is not in use or if bindEvents is false
- if (!opts.bindEvents || (b && !opts.showOverlay))
- return;
-
- // bind anchors and inputs for mouse and key events
- var events = 'mousedown mouseup keydown keypress';
- b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler);
-
-// former impl...
-// var $e = $('a,:input');
-// b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
-};
-
-// event handler to suppress keyboard/mouse events when blocking
-function handler(e) {
- // allow tab navigation (conditionally)
- if (e.keyCode && e.keyCode == 9) {
- if (pageBlock && e.data.constrainTabKey) {
- var els = pageBlockEls;
- var fwd = !e.shiftKey && e.target == els[els.length-1];
- var back = e.shiftKey && e.target == els[0];
- if (fwd || back) {
- setTimeout(function(){focus(back)},10);
- return false;
- }
- }
- }
- // allow events within the message content
- if ($(e.target).parents('div.blockMsg').length > 0)
- return true;
-
- // allow events for content that is not being blocked
- return $(e.target).parents().children().filter('div.blockUI').length == 0;
-};
-
-function focus(back) {
- if (!pageBlockEls)
- return;
- var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
- if (e)
- e.focus();
-};
-
-function center(el, x, y) {
- var p = el.parentNode, s = el.style;
- var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
- var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
- if (x) s.left = l > 0 ? (l+'px') : '0';
- if (y) s.top = t > 0 ? (t+'px') : '0';
-};
-
-function sz(el, p) {
- return parseInt($.css(el,p))||0;
-};
-
-})(jQuery);
\ No newline at end of file
+/*
+* jQuery BlockUI; v20141123
+* http://jquery.malsup.com/block/
+* Copyright (c) 2014 M. Alsup; Dual licensed: MIT/GPL
+*/
+(function(){"use strict";function e(e){function o(o,i){var s,h,k=o==window,v=i&&void 0!==i.message?i.message:void 0;if(i=e.extend({},e.blockUI.defaults,i||{}),!i.ignoreIfBlocked||!e(o).data("blockUI.isBlocked")){if(i.overlayCSS=e.extend({},e.blockUI.defaults.overlayCSS,i.overlayCSS||{}),s=e.extend({},e.blockUI.defaults.css,i.css||{}),i.onOverlayClick&&(i.overlayCSS.cursor="pointer"),h=e.extend({},e.blockUI.defaults.themedCSS,i.themedCSS||{}),v=void 0===v?i.message:v,k&&b&&t(window,{fadeOut:0}),v&&"string"!=typeof v&&(v.parentNode||v.jquery)){var y=v.jquery?v[0]:v,m={};e(o).data("blockUI.history",m),m.el=y,m.parent=y.parentNode,m.display=y.style.display,m.position=y.style.position,m.parent&&m.parent.removeChild(y)}e(o).data("blockUI.onUnblock",i.onUnblock);var g,I,w,U,x=i.baseZ;g=r||i.forceIframe?e('<iframe class="blockUI" style="z-index:'+x++ +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+i.iframeSrc+'"></iframe>'):e('<div class="blockUI" style="display:none"></div>'),I=i.theme?e('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+x++ +';display:none"></div>'):e('<div class="blockUI blockOverlay" style="z-index:'+x++ +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>'),i.theme&&k?(U='<div class="blockUI '+i.blockMsgClass+' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(x+10)+';display:none;position:fixed">',i.title&&(U+='<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(i.title||" ")+"</div>"),U+='<div class="ui-widget-content ui-dialog-content"></div>',U+="</div>"):i.theme?(U='<div class="blockUI '+i.blockMsgClass+' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(x+10)+';display:none;position:absolute">',i.title&&(U+='<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(i.title||" ")+"</div>"),U+='<div class="ui-widget-content ui-dialog-content"></div>',U+="</div>"):U=k?'<div class="blockUI '+i.blockMsgClass+' blockPage" style="z-index:'+(x+10)+';display:none;position:fixed"></div>':'<div class="blockUI '+i.blockMsgClass+' blockElement" style="z-index:'+(x+10)+';display:none;position:absolute"></div>',w=e(U),v&&(i.theme?(w.css(h),w.addClass("ui-widget-content")):w.css(s)),i.theme||I.css(i.overlayCSS),I.css("position",k?"fixed":"absolute"),(r||i.forceIframe)&&g.css("opacity",0);var C=[g,I,w],S=k?e("body"):e(o);e.each(C,function(){this.appendTo(S)}),i.theme&&i.draggable&&e.fn.draggable&&w.draggable({handle:".ui-dialog-titlebar",cancel:"li"});var O=f&&(!e.support.boxModel||e("object,embed",k?null:o).length>0);if(u||O){if(k&&i.allowBodyStretch&&e.support.boxModel&&e("html,body").css("height","100%"),(u||!e.support.boxModel)&&!k)var E=d(o,"borderTopWidth"),T=d(o,"borderLeftWidth"),M=E?"(0 - "+E+")":0,B=T?"(0 - "+T+")":0;e.each(C,function(e,o){var t=o[0].style;if(t.position="absolute",2>e)k?t.setExpression("height","Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:"+i.quirksmodeOffsetHack+') + "px"'):t.setExpression("height",'this.parentNode.offsetHeight + "px"'),k?t.setExpression("width",'jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'):t.setExpression("width",'this.parentNode.offsetWidth + "px"'),B&&t.setExpression("left",B),M&&t.setExpression("top",M);else if(i.centerY)k&&t.setExpression("top",'(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'),t.marginTop=0;else if(!i.centerY&&k){var n=i.css&&i.css.top?parseInt(i.css.top,10):0,s="((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "+n+') + "px"';t.setExpression("top",s)}})}if(v&&(i.theme?w.find(".ui-widget-content").append(v):w.append(v),(v.jquery||v.nodeType)&&e(v).show()),(r||i.forceIframe)&&i.showOverlay&&g.show(),i.fadeIn){var j=i.onBlock?i.onBlock:c,H=i.showOverlay&&!v?j:c,z=v?j:c;i.showOverlay&&I._fadeIn(i.fadeIn,H),v&&w._fadeIn(i.fadeIn,z)}else i.showOverlay&&I.show(),v&&w.show(),i.onBlock&&i.onBlock.bind(w)();if(n(1,o,i),k?(b=w[0],p=e(i.focusableElements,b),i.focusInput&&setTimeout(l,20)):a(w[0],i.centerX,i.centerY),i.timeout){var W=setTimeout(function(){k?e.unblockUI(i):e(o).unblock(i)},i.timeout);e(o).data("blockUI.timeout",W)}}}function t(o,t){var s,l=o==window,a=e(o),d=a.data("blockUI.history"),c=a.data("blockUI.timeout");c&&(clearTimeout(c),a.removeData("blockUI.timeout")),t=e.extend({},e.blockUI.defaults,t||{}),n(0,o,t),null===t.onUnblock&&(t.onUnblock=a.data("blockUI.onUnblock"),a.removeData("blockUI.onUnblock"));var r;r=l?e("body").children().filter(".blockUI").add("body > .blockUI"):a.find(">.blockUI"),t.cursorReset&&(r.length>1&&(r[1].style.cursor=t.cursorReset),r.length>2&&(r[2].style.cursor=t.cursorReset)),l&&(b=p=null),t.fadeOut?(s=r.length,r.stop().fadeOut(t.fadeOut,function(){0===--s&&i(r,d,t,o)})):i(r,d,t,o)}function i(o,t,i,n){var s=e(n);if(!s.data("blockUI.isBlocked")){o.each(function(){this.parentNode&&this.parentNode.removeChild(this)}),t&&t.el&&(t.el.style.display=t.display,t.el.style.position=t.position,t.el.style.cursor="default",t.parent&&t.parent.appendChild(t.el),s.removeData("blockUI.history")),s.data("blockUI.static")&&s.css("position","static"),"function"==typeof i.onUnblock&&i.onUnblock(n,i);var l=e(document.body),a=l.width(),d=l[0].style.width;l.width(a-1).width(a),l[0].style.width=d}}function n(o,t,i){var n=t==window,l=e(t);if((o||(!n||b)&&(n||l.data("blockUI.isBlocked")))&&(l.data("blockUI.isBlocked",o),n&&i.bindEvents&&(!o||i.showOverlay))){var a="mousedown mouseup keydown keypress keyup touchstart touchend touchmove";o?e(document).bind(a,i,s):e(document).unbind(a,s)}}function s(o){if("keydown"===o.type&&o.keyCode&&9==o.keyCode&&b&&o.data.constrainTabKey){var t=p,i=!o.shiftKey&&o.target===t[t.length-1],n=o.shiftKey&&o.target===t[0];if(i||n)return setTimeout(function(){l(n)},10),!1}var s=o.data,a=e(o.target);return a.hasClass("blockOverlay")&&s.onOverlayClick&&s.onOverlayClick(o),a.parents("div."+s.blockMsgClass).length>0?!0:0===a.parents().children().filter("div.blockUI").length}function l(e){if(p){var o=p[e===!0?p.length-1:0];o&&o.focus()}}function a(e,o,t){var i=e.parentNode,n=e.style,s=(i.offsetWidth-e.offsetWidth)/2-d(i,"borderLeftWidth"),l=(i.offsetHeight-e.offsetHeight)/2-d(i,"borderTopWidth");o&&(n.left=s>0?s+"px":"0"),t&&(n.top=l>0?l+"px":"0")}function d(o,t){return parseInt(e.css(o,t),10)||0}e.fn._fadeIn=e.fn.fadeIn;var c=e.noop||function(){},r=/MSIE/.test(navigator.userAgent),u=/MSIE 6.0/.test(navigator.userAgent)&&!/MSIE 8.0/.test(navigator.userAgent);document.documentMode||0;var f=e.isFunction(document.createElement("div").style.setExpression);e.blockUI=function(e){o(window,e)},e.unblockUI=function(e){t(window,e)},e.growlUI=function(o,t,i,n){var s=e('<div class="growlUI"></div>');o&&s.append("<h1>"+o+"</h1>"),t&&s.append("<h2>"+t+"</h2>"),void 0===i&&(i=3e3);var l=function(o){o=o||{},e.blockUI({message:s,fadeIn:o.fadeIn!==void 0?o.fadeIn:700,fadeOut:o.fadeOut!==void 0?o.fadeOut:1e3,timeout:o.timeout!==void 0?o.timeout:i,centerY:!1,showOverlay:!1,onUnblock:n,css:e.blockUI.defaults.growlCSS})};l(),s.css("opacity"),s.mouseover(function(){l({fadeIn:0,timeout:3e4});var o=e(".blockMsg");o.stop(),o.fadeTo(300,1)}).mouseout(function(){e(".blockMsg").fadeOut(1e3)})},e.fn.block=function(t){if(this[0]===window)return e.blockUI(t),this;var i=e.extend({},e.blockUI.defaults,t||{});return this.each(function(){var o=e(this);i.ignoreIfBlocked&&o.data("blockUI.isBlocked")||o.unblock({fadeOut:0})}),this.each(function(){"static"==e.css(this,"position")&&(this.style.position="relative",e(this).data("blockUI.static",!0)),this.style.zoom=1,o(this,t)})},e.fn.unblock=function(o){return this[0]===window?(e.unblockUI(o),this):this.each(function(){t(this,o)})},e.blockUI.version=2.7,e.blockUI.defaults={message:"<h1>Please wait...</h1>",title:null,draggable:!0,theme:!1,css:{padding:0,margin:0,width:"30%",top:"40%",left:"35%",textAlign:"center",color:"#000",border:"3px solid #aaa",backgroundColor:"#fff",cursor:"wait"},themedCSS:{width:"30%",top:"40%",left:"35%"},overlayCSS:{backgroundColor:"#000",opacity:.6,cursor:"wait"},cursorReset:"default",growlCSS:{width:"350px",top:"10px",left:"",right:"10px",border:"none",padding:"5px",opacity:.6,cursor:"default",color:"#fff",backgroundColor:"#000","-webkit-border-radius":"10px","-moz-border-radius":"10px","border-radius":"10px"},iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank",forceIframe:!1,baseZ:1e3,centerX:!0,centerY:!0,allowBodyStretch:!0,bindEvents:!0,constrainTabKey:!0,fadeIn:200,fadeOut:400,timeout:0,showOverlay:!0,focusInput:!0,focusableElements:":input:enabled:visible",onBlock:null,onUnblock:null,onOverlayClick:null,quirksmodeOffsetHack:4,blockMsgClass:"blockMsg",ignoreIfBlocked:!1};var b=null,p=[]}"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],e):e(jQuery)})();
\ No newline at end of file
-(function(jQuery){jQuery.fn.extend({elastic:function(){var mimics=['paddingTop','paddingRight','paddingBottom','paddingLeft','fontSize','lineHeight','fontFamily','width','fontWeight'];return this.each(function(){if(this.type!='textarea'){return false;}
-var $textarea=jQuery(this),$twin=jQuery('<div />').css({'position':'absolute','display':'none','word-wrap':'break-word'}),lineHeight=parseInt($textarea.css('line-height'),10)||parseInt($textarea.css('font-size'),'10'),minheight=parseInt($textarea.css('height'),10)||lineHeight*3,maxheight=parseInt($textarea.css('max-height'),10)||Number.MAX_VALUE,goalheight=0,i=0;if(maxheight<0){maxheight=Number.MAX_VALUE;}
-$twin.appendTo($textarea.parent());var i=mimics.length;while(i--){$twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));}
-function setHeightAndOverflow(height,overflow){curratedHeight=Math.floor(parseInt(height,10));if($textarea.height()!=curratedHeight){$textarea.css({'height':curratedHeight+'px','overflow':overflow});}}
-function update(){var textareaContent=$textarea.val().replace(/&/g,'&').replace(/ /g,' ').replace(/<|>/g,'>').replace(/\n/g,'<br />');var twinContent=$twin.html();if(textareaContent+' '!=twinContent){$twin.html(textareaContent+' ');if(Math.abs($twin.height()+lineHeight-$textarea.height())>3){var goalheight=$twin.height()+lineHeight;if(goalheight>=maxheight){setHeightAndOverflow(maxheight,'auto');}else if(goalheight<=minheight){setHeightAndOverflow(minheight,'hidden');}else{setHeightAndOverflow(goalheight,'hidden');}}}}
-$textarea.css({'overflow':'hidden'});$textarea.keyup(function(){update();});$textarea.live('input paste',function(e){setTimeout(update,250);});update();});}});})(jQuery);
\ No newline at end of file
+/**
+* @name Elastic
+* @descripton Elastic is jQuery plugin that grow and shrink your textareas automatically
+* @version 1.6.11
+* @requires jQuery 1.2.6+
+*
+* @author Jan Jarfalk
+* @author-email jan.jarfalk@unwrongest.com
+* @author-website http://www.unwrongest.com
+*
+* @licence MIT License - http://www.opensource.org/licenses/mit-license.php
+*/
+
+(function($){
+ jQuery.fn.extend({
+ elastic: function() {
+
+ // We will create a div clone of the textarea
+ // by copying these attributes from the textarea to the div.
+ var mimics = [
+ 'paddingTop',
+ 'paddingRight',
+ 'paddingBottom',
+ 'paddingLeft',
+ 'fontSize',
+ 'lineHeight',
+ 'fontFamily',
+ 'width',
+ 'fontWeight',
+ 'border-top-width',
+ 'border-right-width',
+ 'border-bottom-width',
+ 'border-left-width',
+ 'borderTopStyle',
+ 'borderTopColor',
+ 'borderRightStyle',
+ 'borderRightColor',
+ 'borderBottomStyle',
+ 'borderBottomColor',
+ 'borderLeftStyle',
+ 'borderLeftColor'
+ ];
+
+ return this.each( function() {
+
+ // Elastic only works on textareas
+ if ( this.type !== 'textarea' ) {
+ return false;
+ }
+
+ var $textarea = jQuery(this),
+ $twin = jQuery('<div />').css({
+ 'position' : 'absolute',
+ 'display' : 'none',
+ 'word-wrap' : 'break-word',
+ 'white-space' :'pre-wrap'
+ }),
+ lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
+ minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
+ maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
+ goalheight = 0;
+
+ // Opera returns max-height of -1 if not set
+ if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
+
+ // Append the twin to the DOM
+ // We are going to meassure the height of this, not the textarea.
+ $twin.appendTo($textarea.parent());
+
+ // Copy the essential styles (mimics) from the textarea to the twin
+ var i = mimics.length;
+ while(i--){
+ $twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
+ }
+
+ // Updates the width of the twin. (solution for textareas with widths in percent)
+ function setTwinWidth(){
+ var curatedWidth = Math.floor(parseInt($textarea.width(),10));
+ if($twin.width() !== curatedWidth){
+ $twin.css({'width': curatedWidth + 'px'});
+
+ // Update height of textarea
+ update(true);
+ }
+ }
+
+ // Sets a given height and overflow state on the textarea
+ function setHeightAndOverflow(height, overflow){
+
+ var curratedHeight = Math.floor(parseInt(height,10));
+ if($textarea.height() !== curratedHeight){
+ $textarea.css({'height': curratedHeight + 'px','overflow':overflow});
+ }
+ }
+
+ // This function will update the height of the textarea if necessary
+ function update(forced) {
+
+ // Get curated content from the textarea.
+ var textareaContent = $textarea.val().replace(/&/g,'&').replace(/ {2}/g, ' ').replace(/<|>/g, '>').replace(/\n/g, '<br />');
+
+ // Compare curated content with curated twin.
+ var twinContent = $twin.html().replace(/<br>/ig,'<br />');
+
+ if(forced || textareaContent+' ' !== twinContent){
+
+ // Add an extra white space so new rows are added when you are at the end of a row.
+ $twin.html(textareaContent+' ');
+
+ // Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
+ if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
+
+ var goalheight = $twin.height()+lineHeight;
+ if(goalheight >= maxheight) {
+ setHeightAndOverflow(maxheight,'auto');
+ } else if(goalheight <= minheight) {
+ setHeightAndOverflow(minheight,'hidden');
+ } else {
+ setHeightAndOverflow(goalheight,'hidden');
+ }
+
+ }
+
+ }
+
+ }
+
+ // Hide scrollbars
+ $textarea.css({'overflow':'hidden'});
+
+ // Update textarea size on keyup, change, cut and paste
+ $textarea.bind('keyup change cut paste', function(){
+ update();
+ });
+
+ // Update width of twin if browser or textarea is resized (solution for textareas with widths in percent)
+ jQuery(window).bind('resize', setTwinWidth);
+ $textarea.bind('resize', setTwinWidth);
+ $textarea.bind('update', update);
+
+ // Compact textarea on blur
+ $textarea.bind('blur',function(){
+ if($twin.height() < maxheight){
+ if($twin.height() > minheight) {
+ $textarea.height($twin.height());
+ } else {
+ $textarea.height(minheight);
+ }
+ }
+ });
+
+ // And this line is to catch the browser paste event
+ $textarea.bind('input paste',function(e){ setTimeout( update, 250); });
+
+ // Run update once when elastic is initialized
+ update();
+
+ });
+
+ }
+ });
+})(jQuery);
\ No newline at end of file
(function($) {
+var jquery_gt_18 = jQuery.fn.jquery >= "1.8";
// Some common default namespaces, that are treated specially by browsers.
//
var default_xmlns = {
"html": "http://www.w3.org/1999/xhtml/"
};
+rbackslash = /\\(?!\\)/g;
// A reverse mapping for common namespace prefixes.
//
//
// This logic is taken straight from the jQuery/Sizzle sources.
//
-var setExprMatchRegex = function(type,regex) {
- $.expr.match[type] = new RegExp(regex.source + /(?![^\[]*\])(?![^\(]*\))/.source);
- if($.expr.leftMatch) {
- $.expr.leftMatch[type] = new RegExp(/(^(?:.|\r|\n)*?)/.source + $.expr.match[type].source.replace(/\\(\d+)/g, function(all, num){
- return "\\" + (num - 0 + 1);
- }));
- }
-}
-
-
+var setExprMatchRegex = (jquery_gt_18) ? function(type,regex) {
+ $.expr.match[type] = regex;
+ } : function(type,regex) {
+ $.expr.match[type] = new RegExp(regex.source + /(?![^\[]*\])(?![^\(]*\))/.source);
+ if($.expr.leftMatch) {
+ $.expr.leftMatch[type] = new RegExp(/(^(?:.|\r|\n)*?)/.source + $.expr.match[type].source.replace(/\\(\d+)/g, function(all, num){
+ return "\\" + (num - 0 + 1);
+ }));
+ }
+ };
// Modify the TAG match regexp to include optional namespace selector.
// This is basically (namespace|)?(tagname).
//
-setExprMatchRegex("TAG",/^((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)))/);
-
+if (jquery_gt_18) {
+ setExprMatchRegex("TAG",/^((?:((?:\\.|[-\w*]|[^\x00-\xa0])+\|)?((?:\\.|[-\w*]|[^\x00-\xa0])+)))/);
+} else {
+ setExprMatchRegex("TAG",/^((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)))/);
+}
// Perform some capability-testing.
//
//
div = null;
-
-// Modify the TAG find function to account for a namespace selector.
-//
-$.expr.find.TAG = function(match,context,isXML) {
+function find_tag_17(match, context, isXML) {
var ns = getNamespaceURI(match[2]);
var ln = match[3];
- var res;
+ return find_tag(ns, ln, match, context, isXML);
+}
+
+function find_tag_18(match, context, isXML) {
+ var index = match.indexOf("|");
+
+ if (index == -1) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName(match);
+ }
+ }
+
+ var ns = getNamespaceURI(match.substring(0,index + 1));
+ var ln = match.substring(index + 1);
+
+ return find_tag(ns, ln, match, context, isXML);
+}
+
+function find_tag(ns, ln, match, context, isXML) {
+ var res;
if(typeof context.getElementsByTagNameNS != "undefined") {
// Easy case - we have getElementsByTagNameNS
res = context.getElementsByTagNameNS(ns,ln);
}
}
return res;
-};
+}
+// Modify the TAG find function to account for a namespace selector.
+//
+$.expr.find.TAG = (jquery_gt_18)? find_tag_18 : find_tag_17;
// Check whether a node is part of an XML document.
// Copied verbatim from jQuery sources, needed in TAG preFilter below.
!!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
};
-
// Modify the TAG preFilter function to work with modified match regexp.
// This normalises case of the tag name if we're in a HTML document.
//
-$.expr.preFilter.TAG = function(match, curLoop, inplace, result, not, isXML) {
+$.expr.preFilter.TAG = (jquery_gt_18)? function(match, curLoop, inplace, result, not, isXML) {
var ln = match[3];
if(!isXML) {
if(localname_is_uppercase) {
ln = ln.toLowerCase();
}
}
- return [match[0],getNamespaceURI(match[2]),ln];
+ return [match[0], match[0],getNamespaceURI(match[2]),ln];
+} : function(match, curLoop, inplace, result, not, isXML) {
+ var ln = match[3];
+ if(!isXML) {
+ if(localname_is_uppercase) {
+ ln = ln.toUpperCase();
+ } else {
+ ln = ln.toLowerCase();
+ }
+ }
+ return [match[0],getNamespaceURI(match[2]),ln];
};
+function filter_tag(ln, ns) {
+ var index = ln.indexOf("|");
+ if (index != -1)
+ ln = ln.substring(index+1);
+ return function( elem ) {
+ var e_ns = elem.namespaceURI ? elem.namespaceURI : elem.tagUrn;
+ var e_ln = elem.localName ? elem.localName : elem.tagName;
+ if(ns == "*" || e_ns == ns || (ns == "" && !e_ns)) {
+ return ((ln == "*" && elem.nodeType == 1) || e_ln == ln);
+ }
+
+ return false;
+ };
+};
-// Modify the TAG filter function to account for a namespace selector.
+//Modify the TAG filter function to account for a namespace selector.
//
-$.expr.filter.TAG = function(elem,match) {
- var ns = match[1];
+$.expr.filter.TAG = (jquery_gt_18)? filter_tag : function(elem, match) {
+ var ns = match[1];
var ln = match[2];
- var e_ns = elem.namespaceURI ? elem.namespaceURI : elem.tagUrn;
- var e_ln = elem.localName ? elem.localName : elem.tagName;
- if(ns == "*" || e_ns == ns || (ns == "" && !e_ns)) {
- return ((ln == "*" && elem.nodeType == 1) || e_ln == ln);
- }
- return false;
+ return filter_tag(ln, ns).call(this, elem, null);
};
-
// Modify the ATTR match regexp to extract a namespace selector.
// This is basically ([namespace|])(attrname)(op)(quote)(pattern)(quote)
//
-setExprMatchRegex("ATTR",/\[\s*((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF_-]|\\.)+)))\s*(?:(\S?=)\s*(['"]*)(.*?)\5|)\s*\]/);
-
+if (jquery_gt_18) {
+ setExprMatchRegex("ATTR",/^\[[\x20\t\r\n\f]*(((?:\\.|[-\w]|[^\x00-\xa0])+\|)?((?:\\.|[-\w]|[^\x00-\xa0])+))[\x20\t\r\n\f]*(?:([*^$|!~]?=)[\x20\t\r\n\f]*(?:(['"])((?:\\.|[^\\])*?)\5|((?:\\.|[-\w]|[^\x00-\xa0])+\|)?((?:\\.|[-\w#]|[^\x00-\xa0])+)|)|)[\x20\t\r\n\f]*\]/);
+} else {
+ setExprMatchRegex("ATTR",/\[\s*((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF_-]|\\.)+)))\s*(?:(\S?=)\s*(['"]*)(.*?)\5|)\s*\]/);
+}
// Modify the ATTR preFilter function to account for new regexp match groups,
// and normalise the namespace URI.
//
-$.expr.preFilter.ATTR = function(match, curLoop, inplace, result, not, isXML) {
+$.expr.preFilter.ATTR = (jquery_gt_18)? function( match, context, isXml ) {
+ var name = match[1].replace(rbackslash, "");
+
+ if( match[4] == "~=" ) {
+ match[6] = " " + match[6] + " ";
+ }
+
+ // Move the given value to match[5] whether quoted or unquoted
+ match[5] = ( match[6] || match[7] || "" ).replace( rbackslash, "" );
+
+ if(!match[2] || match[2] == "|") {
+ match[2] = "";
+ } else {
+ match[2] = getNamespaceURI(match[2]);
+ }
+
+ return match.slice( 0, 6 );
+} : function(match, curLoop, inplace, result, not, isXML) {
var name = match[3].replace(/\\/g, "");
if(!isXML && $.expr.attrMap[name]) {
match[3] = $.expr.attrMap[name];
};
-// Modify the ATTR filter function to account for namespace selector.
-// Unfortunately this means factoring out the attribute-checking code
-// into a separate function, since it might be called multiple times.
-//
-var filter_attr = function(result,type,check) {
- var value = result + "";
- return result == null ?
- type === "!=" :
- type === "=" ?
- value === check :
- type === "*=" ?
- value.indexOf(check) >= 0 :
- type === "~=" ?
- (" " + value + " ").indexOf(check) >= 0 :
- !check ?
- value && result !== false :
- type === "!=" ?
- value != check :
- type === "^=" ?
- value.indexOf(check) === 0 :
- type === "$=" ?
- value.substr(value.length - check.length) === check :
- type === "|=" ?
- value === check || value.substr(0,check.length+1)===check+"-" :
- false;
-}
+var attr_op_eval = function(result, operator, check) {
+ if ( result == null ) {
+ return operator === "!=";
+ }
+
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.substr( result.length - check.length ) === check :
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
+ false;
+};
+
+//Modify the ATTR filter function to account for namespace selector.
+//Unfortunately this means factoring out the attribute-checking code
+//into a separate function, since it might be called multiple times.
+//
+function filter_attr( prefixedName, ns, name, op, check ) {
+ return function( elem, context ) {
+ if(ns == "") {
+ var result = $(elem).attr(name);
+ return attr_op_eval(result, op, check);
+ } else {
+ if(ns != "*" && typeof elem.getAttributeNS != "undefined") {
+ return attr_op_eval(elem.getAttributeNS(ns,name), op, check);
+ }
+
+ // Need to iterate over all attributes, either because we couldn't
+ // look it up or because we need to match all namespaces.
+ var attrs = elem.attributes;
+ for (var i=0; attrs[i]; i++) {
+ var ln = attrs[i].localName;
+ if(!ln) {
+ ln = attrs[i].nodeName;
+ var idx = ln.indexOf(":");
+ if(idx >= 0) {
+ ln = ln.substr(idx+1);
+ }
+ }
+ if(ln == name) {
+ result = attrs[i].nodeValue;
+ if(ns == "*" || attrs[i].namespaceURI == ns) {
+ if(attr_op_eval(result, op, check)) {
+ return true;
+ }
+ }
+ if(attrs[i].namespaceURI === "" && attrs[i].prefix) {
+ if(attrs[i].prefix == default_xmlns_rev[ns]) {
+ if(attr_op_eval(result, op, check)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+ };
+};
-$.expr.filter.ATTR = function(elem, match) {
- var ns = match[2];
+$.expr.filter.ATTR = (jquery_gt_18)? filter_attr : function(elem, match) {
+ var ns = match[2];
var name = match[3];
var type = match[4];
var check = match[6];
- var result;
- // No namespace, just use ordinary attribute lookup.
- if(ns == "") {
- result = $.expr.attrHandle[name] ?
- $.expr.attrHandle[name](elem) :
- elem[name] != null ?
- elem[name] :
- elem.getAttribute(name);
- return filter_attr(result,type,check);
- }
- // Directly use getAttributeNS if applicable and available
- if(ns != "*" && typeof elem.getAttributeNS != "undefined") {
- return filter_attr(elem.getAttributeNS(ns,name),type,check);
- }
- // Need to iterate over all attributes, either because we couldn't
- // look it up or because we need to match all namespaces.
- var attrs = elem.attributes;
- for(var i=0; attrs[i]; i++) {
- var ln = attrs[i].localName;
- if(!ln) {
- ln = attrs[i].nodeName
- var idx = ln.indexOf(":");
- if(idx >= 0) {
- ln = ln.substr(idx+1);
- }
- }
- if(ln == name) {
- result = attrs[i].nodeValue;
- if(ns == "*" || attrs[i].namespaceURI == ns) {
- if(filter_attr(result,type,check)) {
- return true;
- }
- }
- if(attrs[i].namespaceURI === "" && attrs[i].prefix) {
- if(attrs[i].prefix == default_xmlns_rev[ns]) {
- if(filter_attr(result,type,check)) {
- return true;
- }
- }
- }
- }
- }
- return false;
+ return filter_attr(null, ns, name, type, check).call(this, elem, null);
};
-})(jQuery);
-
+})(jQuery);
\ No newline at end of file
$.wiki.activePerspective = function() {
- return this.perspectives[$("#tabs li.active").attr('id')];
+ return this.perspectives[$("#tabs li a.active").parent().attr('id')];
};
$.wiki.exitContext = function() {
$.wiki.newTab = function(doc, title, klass) {
var base_id = 'id' + Math.floor(Math.random()* 5000000000);
var id = (''+klass)+'_' + base_id;
- var $tab = $('<li id="'+id+'" data-ui-related="'+base_id+'" data-ui-jsclass="'+klass+'" >'
- + title + '<img src="'+STATIC_URL+'icons/close.png" class="tabclose"></li>');
+ var $tab = $('<li class="nav-item" id="'+id+'" data-ui-related="'+base_id+'" data-ui-jsclass="'+klass+'" ><a href="#" class="nav-link">'
+ + title + ' <span class="badge badge-danger tabclose">x</span></a></li>');
var $view = $('<div class="editor '+klass+'" id="'+base_id+'"> </div>');
this.perspectives[id] = new $.wiki[klass]({
return this.perspectives[ $(tab).attr('id')];
}
- $.wiki.switchToTab = function(tab){
- var self = this;
- var $tab = $(tab);
+ $.wiki.switchToTab = function(tab){
+ var self = this;
+ var $tab = $(tab);
- if($tab.length != 1)
- $tab = $(DEFAULT_PERSPECTIVE);
+ if($tab.length != 1)
+ $tab = $(DEFAULT_PERSPECTIVE);
- var $old = $tab.closest('.tabs').find('.active');
+ var $old_a = $tab.closest('.tabs').find('.active');
- $old.each(function(){
- $(this).removeClass('active');
- self.perspectives[$(this).attr('id')].onExit();
- $('#' + $(this).attr('data-ui-related')).hide();
- });
+ $old_a.each(function(){
+ var tab = $(this).parent()
+ $(this).removeClass('active');
+ self.perspectives[tab.attr('id')].onExit();
+ $('#' + tab.attr('data-ui-related')).hide();
+ });
- /* show new */
- $tab.addClass('active');
- $('#' + $tab.attr('data-ui-related')).show();
+ /* show new */
+ $('a', tab).addClass('active');
+ $('#' + $tab.attr('data-ui-related')).show();
- console.log($tab);
- console.log($.wiki.perspectives);
+ console.log($tab);
+ console.log($.wiki.perspectives);
- $.wiki.perspectives[$tab.attr('id')].onEnter();
- };
+ $.wiki.perspectives[$tab.attr('id')].onEnter();
+ };
/*
* Basic perspective.
$(function()
{
- var tabs = $('ol#tabs li');
- var gallery = null;
+ var tabs = $('ol#tabs li');
+ var gallery = null;
+ var MIN_SIDEBAR_WIDTH = 50,
+ DEFAULT_SIDEBAR_WIDTH = 480;
+
CurrentDocument = new $.wikiapi.WikiDocument("document-meta");
$.blockUI.defaults.baseZ = 10000;
function initialize()
{
var splitter = $('#splitter'),
- editors = $('#editor .editor'),
- vsplitbar = $('.vsplitbar'),
+ vsplitbar = $('#vsplitbar'),
sidebar = $('#sidebar'),
dragLayer = $('#drag-layer'),
vsplitbarWidth = vsplitbar.outerWidth(),
isHolding = false;
- // Moves panes so that left border of the vsplitbar lands x pixels from the left border of the splitter
- function setSplitbarAt(x) {
- var right = splitterWidth - x;
- editors.each(function() {
- this.style.right = right + 'px';
- });
- vsplitbar[0].style.right = sidebar[0].style.width = (right - vsplitbarWidth) + 'px';
- };
+ function setSidebarWidth(x) {
+ if (x < MIN_SIDEBAR_WIDTH) {
+ x = 0;
+ vsplitbar.removeClass('active');
+ } else {
+ vsplitbar.addClass('active');
+ }
+ $.wiki.state.perspectives.ScanGalleryPerspective.width = x;
+ sidebar[0].style.width = x + 'px';
+ };
- $(document).keydown(function(event) {
- console.log("Received key:", event);
- });
/* The save button */
$('#save-button').click(function(event){
/*
* TABS
*/
- $('.tabs li').live('click', function(event, callback) {
+ $(document).on('click', '.tabs li', function(event, callback) {
event.preventDefault();
$.wiki.switchToTab(this);
});
- $('#tabs li > .tabclose').live('click', function(event, callback) {
- var $tab = $(this).parent();
+ $(document).on('click', '#tabs li .tabclose', function(event, callback) {
+ var $tab = $(this).parent().parent();
- if($tab.is('.active'))
+ if($('a', $tab).is('.active'))
$.wiki.switchToTab(DEFAULT_PERSPECTIVE);
var p = $.wiki.perspectiveForTab($tab);
$(window).resize(function(){
- $('iframe').height($(window).height() - $('#tabs').outerHeight() - $('#source-editor .toolbar').outerHeight());
splitterWidth = splitter.width();
});
$(window).resize();
-
- vsplitbar.toggle(
- function() {
- $.wiki.state.perspectives.ScanGalleryPerspective.show = true;
- setSplitbarAt(splitterWidth - (480 + vsplitbarWidth));
- $('.vsplitbar').addClass('active');
- $(window).resize();
- $.wiki.perspectiveForTab('#tabs-right .active').onEnter();
- },
- function() {
- var active_right = $.wiki.perspectiveForTab('#tabs-right .active');
- $.wiki.state.perspectives.ScanGalleryPerspective.show = false;
- $(".vsplitbar-title").html("↑ " + active_right.vsplitbar + " ↑");
- setSplitbarAt(splitterWidth - vsplitbarWidth);
- $('.vsplitbar').removeClass('active');
- $(window).resize();
- active_right.onExit();
- }
- );
+ $.wiki.perspectiveForTab($('#tabs-right .active').parent()).onEnter();
+
+ vsplitbar.on('click', function() {
+ var $this = $(this);
+ if ($this.hasClass('active')) {
+ $.wiki.state.perspectives.ScanGalleryPerspective.lastWidth = sidebar.width();
+ setSidebarWidth(0);
+ } else {
+ setSidebarWidth($.wiki.state.perspectives.ScanGalleryPerspective.lastWidth);
+ }
+ });
/* Splitbar dragging support */
.mousedown(function(e) {
e.preventDefault();
isHolding = true;
+ if (sidebar.width() > MIN_SIDEBAR_WIDTH) {
+ $.wiki.state.perspectives.ScanGalleryPerspective.lastWidth = sidebar.width();
+ }
})
.mousemove(function(e) {
if(isHolding) {
dragLayer.show(); // We don't show it up until now so that we don't lose single click events on vsplitbar
}
});
- dragLayer.mousemove(function(e) {
- setSplitbarAt(e.clientX - vsplitbarWidth/2);
- });
- $('body').mouseup(function(e) {
- dragLayer.hide();
- isHolding = false;
- });
+ dragLayer.mousemove(function(e) {
+ setSidebarWidth(splitterWidth - e.clientX - vsplitbarWidth / 2);
+ });
+ $('body').mouseup(function(e) {
+ dragLayer.hide();
+ isHolding = false;
+ });
+ setSidebarWidth($.wiki.state.perspectives.ScanGalleryPerspective.width);
- if($.wiki.state.perspectives.ScanGalleryPerspective.show){
- $('.vsplitbar').trigger('click');
- $(".vsplitbar-title").html("↓ GALERIA ↓");
- } else {
- $(".vsplitbar-title").html("↑ GALERIA ↑");
- }
- window.onbeforeunload = function(e) {
+ window.onbeforeunload = function(e) {
if($.wiki.isDirty()) {
e.returnValue = "Na stronie mogą być nie zapisane zmiany.";
return "Na stronie mogą być nie zapisane zmiany.";
/*
* TABS
*/
- $('#tabs li').live('click', function(event, callback) {
+ $(document).on('click', '#tabs li', function(event, callback) {
event.preventDefault();
$.wiki.switchToTab(this);
});
- $('#tabs li > .tabclose').live('click', function(event, callback) {
+ $(document).on('click', '#tabs li > .tabclose', function(event, callback) {
var $tab = $(this).parent();
if($tab.is('.active'))
/*
* Initialize all perspectives
*/
- initAll( $.makeArray($('ol#tabs li')), initialize);
+ initAll( $.makeArray($('#tabs li')), initialize);
console.log(location.hash);
-});
\ No newline at end of file
+});
});
});
- $toolbar.children().filter('select').change(function(event){
+ $('select', $toolbar).change(function(event){
var slug = $(this).val();
$container.scrollLeft(0);
- $('*[data-group]').hide().filter('[data-group=' + slug + ']').show();
+ $('*[data-group]').hide().filter('[data-group="' + slug + '"]').show();
}).change();
$('button.next', $toolbar).click(function() {
};
-})(jQuery);
\ No newline at end of file
+})(jQuery);
function AnnotationsPerspective(options){
var old_callback = options.callback || function() { };
- this.noupdate_hash_onenter = true;
this.vsplitbar = 'PRZYPISY';
options.callback = function(){
self.$annos.hide();
self.$error.hide();
- self.$spinner.show(100, function(){
+ self.$spinner.fadeIn(100, function() {
self.refresh(self, atype);
});
});
old_callback.call(this);
};
- $.wiki.Perspective.call(this, options);
+ $.wiki.SidebarPerspective.call(this, options);
}
- AnnotationsPerspective.prototype = new $.wiki.Perspective();
+ AnnotationsPerspective.prototype = new $.wiki.SidebarPerspective();
AnnotationsPerspective.prototype.updateAnnotationIds = function(self){
self.annotationToAnchor = {};
},
error: function(text) {
$.unblockUI();
- self.$error.html(text);
+ self.$error.html('<div class="alert alert-danger">' + text + '</div>');
self.$spinner.hide();
self.$error.show();
}
var persp = $.wiki.activePerspective();
if (persp == 'CodeMirrorPerspective') {
- xml = $.wiki.perspectives[persp].codemirror.getCode();
+ xml = $.wiki.perspectives[persp].codemirror.getValue();
}
else if (persp == 'VisualPerspective') {
html2text({
xml = text;
},
error: function(text){
- self.$error.html('<p>Wystąpił błąd:</p><pre>' + text + '</pre>');
+ self.$error.html('<div class="alert alert-danger"><p>Wystąpił błąd:</p><pre>' + text + '</pre></div>');
self.$spinner.hide();
self.$error.show();
}
var error = $('parsererror', doc);
if (error.length > 0) {
- self.$error.html('Błąd parsowania XML.');
+ self.$error.html('<div class="alert alert-danger">Błąd parsowania XML.</a>');
self.$spinner.hide();
self.$error.show();
}
if (annos.length == 0)
{
- self.$annos.html('Nie ma żadnych przypisów');
+ self.$annos.html('<div class="alert alert-info">Nie ma żadnych przypisów</div>');
self.$spinner.hide();
self.$annos.show();
}
}(xml_text),
error: function(text) {
$.unblockUI();
- self.$error.html(text);
+ self.$error.html('<div class="alert alert-danger">' + text + '</div>');
self.$spinner.hide();
self.$error.show();
}
AnnotationsPerspective.prototype.onEnter = function(){
- $.wiki.Perspective.prototype.onEnter.call(this);
+ $.wiki.SidebarPerspective.prototype.onEnter.call(this);
- $('.vsplitbar').not('.active').trigger('click');
- $(".vsplitbar-title").html("↓ PRZYPISY ↓");
this.$refresh.filter('.active').trigger('click');
};
-/* COMMENT */
(function($) {
- function CodeMirrorPerspective(options)
- {
- var old_callback = options.callback;
+ function CodeMirrorPerspective(options) {
+ var old_callback = options.callback;
options.callback = function(){
- var self = this;
-
- this.codemirror = CodeMirror.fromTextArea('codemirror_placeholder', {
- parserfile: 'parsexml.js',
- path: STATIC_URL + "js/lib/codemirror-0.8/",
- stylesheet: STATIC_URL + "css/xmlcolors_20100906.css",
- parserConfig: {
- useHTMLKludges: false
- },
- iframeClass: 'xml-iframe',
- textWrapping: true,
- lineNumbers: true,
- width: "100%",
- height: "100%",
- tabMode: 'default',
- indentUnit: 0,
- readOnly: CurrentDocument.readonly || false,
- initCallback: function(){
-
- self.codemirror.grabKeys(function(event) {
- if (event.button) {
- $(event.button).trigger('click');
- event.button = null;
- }
- }, function(keycode, event) {
- if(!event.altKey)
- return false;
-
- var c = String.fromCharCode(keycode).toLowerCase();
- var button = $("#source-editor button[data-ui-accesskey='"+c+"']");
- if(button.length == 0)
- return false;
-
- /* it doesn't matter which button we pick - all do the same */
- event.button = button[0];
- return true;
- });
-
- $('#source-editor .toolbar').toolbarize({
- actionContext: self.codemirror
- });
-
- console.log("Initialized CodeMirror");
-
- // textarea is no longer needed
- $('codemirror_placeholder').remove();
-
- old_callback.call(self);
- }
- });
- };
-
- $.wiki.Perspective.call(this, options);
- };
-
-
- CodeMirrorPerspective.prototype = new $.wiki.Perspective();
-
- CodeMirrorPerspective.prototype.freezeState = function() {
- this.config().position = this.codemirror.win.scrollY || 0;
- };
-
- CodeMirrorPerspective.prototype.unfreezeState = function () {
- this.codemirror.win.scroll(0, this.config().position || 0);
- };
+ var self = this;
+
+ this.codemirror = CodeMirror.fromTextArea($(
+ '#codemirror_placeholder').get(0), {
+ mode: 'xml',
+ lineWrapping: true,
+ lineNumbers: true,
+ readOnly: CurrentDocument.readonly || false,
+ identUnit: 0,
+ });
+
+
+ $('#source-editor').keydown(function(event) {
+ if(!event.altKey)
+ return;
+
+ var c = event.key;
+ var button = $("#source-editor button[data-ui-accesskey='"+c+"']");
+ if(button.length == 0)
+ return;
+ button.get(0).click();
+ event.preventDefault();
+ });
+
+ $('#source-editor .toolbar').toolbarize({
+ actionContext: self.codemirror
+ });
+
+ // textarea is no longer needed
+ $('#codemirror_placeholder').remove();
+ old_callback.call(self);
+ }
- CodeMirrorPerspective.prototype.onEnter = function(success, failure) {
- $.wiki.Perspective.prototype.onEnter.call(this);
+ $.wiki.Perspective.call(this, options);
+ };
- console.log('Entering', this.doc);
- this.codemirror.setCode(this.doc.text);
- /* fix line numbers bar */
- var $nums = $('.CodeMirror-line-numbers');
- var barWidth = $nums.width();
+ CodeMirrorPerspective.prototype = new $.wiki.Perspective();
- $(this.codemirror.frame.contentDocument.body).css('padding-left', barWidth);
- // $nums.css('left', -barWidth);
+ CodeMirrorPerspective.prototype.freezeState = function() {
+ this.config().position = this.codemirror.getScrollInfo().top;
+ };
+
+ CodeMirrorPerspective.prototype.unfreezeState = function () {
+ this.codemirror.scrollTo(0, this.config().position || 0);
+ };
+
+ CodeMirrorPerspective.prototype.onEnter = function(success, failure) {
+ $.wiki.Perspective.prototype.onEnter.call(this);
+
+ this.codemirror.setValue(this.doc.text);
- $(window).resize();
this.unfreezeState(this._uistate);
if(success) success();
this.freezeState();
$.wiki.Perspective.prototype.onExit.call(this);
- console.log('Exiting', this.doc);
- this.doc.setText(this.codemirror.getCode());
+ this.doc.setText(this.codemirror.getValue());
- if ($('.vsplitbar').hasClass('active') && $('#SearchPerspective').hasClass('active')) {
- $.wiki.switchToTab('#ScanGalleryPerspective');
- }
+ if ($('.vsplitbar').hasClass('active') && $('#SearchPerspective').hasClass('active')) {
+ $.wiki.switchToTab('#ScanGalleryPerspective');
+ }
- if(success) success();
+ if(success) success();
}
$.wiki.CodeMirrorPerspective = CodeMirrorPerspective;
spoint.insertNode(btag[0])
btag.replaceWith(text);
selection.removeAllRanges();
- openForEdit($('.motyw[theme-class=' + id + ']'));
+ openForEdit($('.motyw[theme-class="' + id + '"]'));
}
});
}
}
tableContent += "</tr>";
- $("#content").append(specialCharsContainer);
+ $("body").append(specialCharsContainer);
// localStorage for recently used characters - reading
if ($origin.is('.motyw')) {
$('.akap-edit-button').remove();
- withThemes(function(canonThemes){
- $('textarea', $overlay).autocomplete(canonThemes, {
- autoFill: true,
- multiple: true,
- selectFirst: true,
- highlight: false
- });
- })
+ $.themes.autocomplete($('textarea', $overlay));
}
if ($origin.is('.motyw')){
$('.delete-button', $overlay).click(function(){
- if (window.confirm("Czy jesteś pewien, że chcesz usunąć ten motyw ?")) {
- $('[theme-class=' + $origin.attr('theme-class') + ']').remove();
+ if (window.confirm("Czy jesteś pewien, że chcesz usunąć ten motyw?")) {
+ $('[theme-class="' + $origin.attr('theme-class') + '"]').remove();
$overlay.remove();
$(document).unbind('click.blur-overlay');
return false;
return false;
});
- $('.edit-button').live('click', function(event){
+ $(document).on('click', '.edit-button', function(event){
event.preventDefault();
openForEdit($(this).parent());
});
}
- $('.motyw').live('click', function(){
+ $(document).on('click', '.motyw', function(){
selectTheme($(this).attr('theme-class'));
});
function ScanGalleryPerspective(options){
var old_callback = options.callback || function() { };
- this.noupdate_hash_onenter = true;
this.vsplitbar = 'GALERIA';
options.callback = function(){
old_callback.call(this);
};
- $.wiki.Perspective.call(this, options);
+ $.wiki.SidebarPerspective.call(this, options);
};
- ScanGalleryPerspective.prototype = new $.wiki.Perspective();
+ ScanGalleryPerspective.prototype = new $.wiki.SidebarPerspective();
ScanGalleryPerspective.prototype._resizeImage = function(){
var $img = this.$image;
* Loading gallery
*/
ScanGalleryPerspective.prototype.onEnter = function(success, failure){
- var self = this;
+ $.wiki.SidebarPerspective.prototype.onEnter.call(this);
- $.wiki.Perspective.prototype.onEnter.call(this);
+ var self = this;
- $('.vsplitbar').not('.active').trigger('click');
- $(".vsplitbar-title").html("↓ GALERIA ↓");
-
this.doc.refreshGallery({
success: function(doc, data){
self.$image.show();
event.preventDefault();
});
- $('#changes-list .entry').live('click', function(){
+ $(document).on('click', '#changes-list .entry', function(){
var $this = $(this);
var selected_count = $("#changes-list .entry.selected").length;
};
$('#history-view-editor .toolbar button').attr('disabled', 'disabled').
- filter('*[data-enabled-when~=' + selected_count + '], *[data-enabled-when~=*]').
+ filter('*[data-enabled-when~="' + selected_count + '"]').
attr('disabled', null);
});
- $('#changes-list span.tag').live('click', function(event){
+ $(document).on('click', '#changes-list span.tag', function(event){
return false;
});
function SearchPerspective(options){
var old_callback = options.callback || function() { };
- this.noupdate_hash_onenter = true;
- this.vsplitbar = 'ZNAJDŹ I ZAMIEŃ';
+ this.vsplitbar = 'ZNAJDŹ I ZAMIEŃ';
options.callback = function(){
var self = this;
old_callback.call(this);
};
- $.wiki.Perspective.call(this, options);
+ $.wiki.SidebarPerspective.call(this, options);
};
- SearchPerspective.prototype = new $.wiki.Perspective();
+ SearchPerspective.prototype = new $.wiki.SidebarPerspective();
SearchPerspective.prototype.search = function(){
var self = this;
self.editor = $.wiki.perspectiveForTab('#CodeMirrorPerspective').codemirror
if (!self.searchCursor) {
+ var options = {};
+ options.caseFold = !self.options['search-case-sensitive'];
+ var start = 0;
+ if (self.options['search-from-cursor']) {
+ start = self.editor.getCursor();
+ }
self.searchCursor = self.editor.getSearchCursor(
self.$searchInput.val(),
- self.options['search-from-cursor'],
- !self.options['search-case-sensitive']
+ start,
+ options
);
}
if (self.searchCursor.findNext()) {
- self.searchCursor.select();
+ self.editor.setSelection(self.searchCursor.from(), self.searchCursor.to());
+ self.editor.scrollIntoView({from: self.searchCursor.from(), to: self.searchCursor.to()}, 20);
self.$replaceButton.removeAttr("disabled");
return true;
}
self.search();
}
else {}
- self.searchCursor.select();
+
+ self.editor.setSelection(self.searchCursor.from(), self.searchCursor.to());
+ self.editor.scrollIntoView({from: self.searchCursor.from(), to: self.searchCursor.to()}, 20);
+
self.searchCursor.replace(query);
if(self.search() && self.options['replace-all']) {
self.replace();
SearchPerspective.prototype.onEnter = function(success, failure){
var self = this;
- $.wiki.Perspective.prototype.onEnter.call(this);
+ $.wiki.SidebarPerspective.prototype.onEnter.call(this);
self.$searchCursor = null;
- $('.vsplitbar').not('.active').trigger('click');
- $(".vsplitbar-title").html("↓ ZNAJDŹ I ZAMIEŃ ↓");
-
if ($.wiki.activePerspective() != 'CodeMirrorPerspective')
$.wiki.switchToTab('#CodeMirrorPerspective');
};
WikiDocument.prototype.refreshGallery = function(params) {
params = $.extend({}, noops, params);
var self = this;
+ if (!self.galleryLink) {
+ params['failure'](self, 'Brak galerii.');
+ return;
+ }
$.ajax({
method: "GET",
url: reverse("ajax_document_gallery", self.galleryLink),
var msg = "Nie udało się wczytać galerii o nazwie: '" + self.galleryLink + "'.";
}
self.galleryImages = [];
- params['failure'](self, "<p>" + msg + "</p>");
+ params['failure'](self, msg);
}
});
};
}
-// Wykonuje block z załadowanymi kanonicznymi motywami
-function withThemes(code_block, onError)
-{
- if (typeof withThemes.canon == 'undefined') {
- $.ajax({
- url: '/editor/themes',
- dataType: 'text',
- success: function(data) {
- withThemes.canon = data.split('\n');
- code_block(withThemes.canon);
- },
- error: function() {
- withThemes.canon = null;
- code_block(withThemes.canon);
- }
- })
- }
- else {
- code_block(withThemes.canon);
- }
-}
-
-
function xml2html(options) {
withStylesheets(function() {
var xml = options.xml.replace(/\/(\s+)/g, '<br />$1');
if (error.length == 0) {
doc = xml2htmlStylesheet.transformToFragment(doc, document);
- console.log(doc.firstChild);
if(doc.firstChild === null) {
options.error("Błąd w przetwarzaniu XML.");
} else {
options.success(doc.childNodes);
- withThemes(function(canonThemes) {
+ $.themes.withCanon(function(canonThemes) {
if (canonThemes != null) {
$('.theme-text-list').addClass('canon').each(function(){
var themes = $(this).html().split(',');
$.wiki.activePerspective = function() {
- return this.perspectives[$("#tabs li.active").attr('id')];
+ return this.perspectives[$("#tabs li a.active").parent().attr('id')];
};
$.wiki.exitContext = function() {
var base_id = 'id' + Math.floor(Math.random()* 5000000000);
var id = (''+klass)+'_' + base_id;
var $tab = $('<li id="'+id+'" data-ui-related="'+base_id+'" data-ui-jsclass="'+klass+'" >'
- + title + '<img src="'+STATIC_URL+'icons/close.png" class="tabclose"></li>');
+ + title + '<img src="'+STATIC_URL+'icons/close.png" class="tabclose"></li>');
+ var $tab = $('<li class="nav-item" id="'+id+'" data-ui-related="'+base_id+'" data-ui-jsclass="'+klass+'" ><a href="#" class="nav-link">'
+ + title + ' <span class="badge badge-danger tabclose">x</span></a></li>');
+
var $view = $('<div class="editor '+klass+'" id="'+base_id+'"> </div>');
this.perspectives[id] = new $.wiki[klass]({
}
$.wiki.switchToTab = function(tab){
- var self = this;
- var $tab = $(tab);
+ var self = this;
+ var $tab = $(tab);
- if($tab.length != 1)
- $tab = $(DEFAULT_PERSPECTIVE);
+ if($tab.length != 1)
+ $tab = $(DEFAULT_PERSPECTIVE);
- var $old = $tab.closest('.tabs').find('.active');
+ var $old_a = $tab.closest('.tabs').find('.active');
- $old.each(function(){
- $(this).removeClass('active');
- self.perspectives[$(this).attr('id')].onExit();
- $('#' + $(this).attr('data-ui-related')).hide();
- });
+ $old_a.each(function(){
+ var tab = $(this).parent();
+ $(this).removeClass('active');
+ self.perspectives[tab.attr('id')].onExit();
+ $('#' + tab.attr('data-ui-related')).hide();
+ });
- /* show new */
- $tab.addClass('active');
- $('#' + $tab.attr('data-ui-related')).show();
+ /* show new */
+ $('a', tab).addClass('active');
+ $('#' + $tab.attr('data-ui-related')).show();
- console.log($tab);
- console.log($.wiki.perspectives);
+ console.log($tab);
+ console.log($.wiki.perspectives);
- $.wiki.perspectives[$tab.attr('id')].onEnter();
+ $.wiki.perspectives[$tab.attr('id')].onEnter();
};
/*
/*
* TABS
*/
- $('.tabs li').live('click', function(event, callback) {
+ $(document).on('click', '.tabs li', function(event, callback) {
event.preventDefault();
$.wiki.switchToTab(this);
});
- $('#tabs li > .tabclose').live('click', function(event, callback) {
- var $tab = $(this).parent();
+ $(document).on('click', '#tabs li .tabclose', function(event, callback) {
+ var $tab = $(this).parent().parent();
- if($tab.is('.active'))
+ if($('a', $tab).is('.active'))
$.wiki.switchToTab(DEFAULT_PERSPECTIVE);
var p = $.wiki.perspectiveForTab($tab);
});
- /*$(window).resize(function(){
- $('iframe').height($(window).height() - $('#tabs').outerHeight() - $('#source-editor .toolbar').outerHeight());
- });
-
- $(window).resize();*/
-
- /*$('.vsplitbar').toggle(
- function() {
- $.wiki.state.perspectives.ScanGalleryPerspective.show = true;
- $('#sidebar').show();
- $('.vsplitbar').css('right', 480).addClass('active');
- $('#editor .editor').css('right', 510);
- $(window).resize();
- $.wiki.perspectiveForTab('#tabs-right .active').onEnter();
- },
- function() {
- var active_right = $.wiki.perspectiveForTab('#tabs-right .active');
- $.wiki.state.perspectives.ScanGalleryPerspective.show = false;
- $('#sidebar').hide();
- $('.vsplitbar').css('right', 0).removeClass('active');
- $(".vsplitbar-title").html("↑ " + active_right.vsplitbar + " ↑");
- $('#editor .editor').css('right', 30);
- $(window).resize();
- active_right.onExit();
- }
- );*/
-
window.onbeforeunload = function(e) {
if($.wiki.isDirty()) {
e.returnValue = "Na stronie mogą być nie zapisane zmiany.";
/*
* TABS
*/
- $('#tabs li').live('click', function(event, callback) {
+ $(document).on('click', '#tabs li', function(event, callback) {
event.preventDefault();
$.wiki.switchToTab(this);
});
- $('#tabs li > .tabclose').live('click', function(event, callback) {
+ $(document).on('click', '#tabs li > .tabclose', function(event, callback) {
var $tab = $(this).parent();
if($tab.is('.active'))
/*
* Initialize all perspectives
*/
- initAll( $.makeArray($('ol#tabs li')), initialize);
+ initAll( $.makeArray($('#tabs li')), initialize);
console.log(location.hash);
});
(function($){
function MotifsPerspective(options){
-
var old_callback = options.callback;
options.callback = function(){
var self = this;
+ this.$editor = $("#motifs-editor");
+ this.object_type_name = "motyw";
+ this.xml_object_type = 'theme';
- self.$tag_name = $('#motifs-editor .tag-name');
- self.$toolbar = $('#motifs-editor .toolbar');
- self.$scrolled = $('#motifs-editor .scrolled');
- withThemes(function(canonThemes){
- self.$tag_name.autocomplete(canonThemes, {
- autoFill: true,
- multiple: true,
- selectFirst: true,
- highlight: false
- });
- })
-
- self.$objects_list = $('#motifs-editor .objects-list');
-
- self.x1 = null;
- self.x2 = null;
- self.y1 = null;
- self.y2 = null;
-
- if (!CurrentDocument.readonly) {
- self.ias = $('#motifs-editor img.area-selectable').imgAreaSelect({ handles: true, onSelectEnd: self._fillCoords(self), instance: true });
- $('#motifs-editor .add').click(self._addObject(self));
+ this._init();
- $('.delete', self.$objects_list).live('click', function() {
- $(this).prev().trigger('click');
- if (window.confirm("Czy na pewno chcesz usunąć ten motyw?")) {
- $(this).prev().remove();
- $(this).remove();
- self._refreshLayout();
- }
- self._resetSelection();
- return false;
- });
- }
-
- $('.image-object', self.$objects_list).live('click', function(){
- $('.active', self.$objects_list).removeClass('active');
- $(this).addClass('active');
- var coords = $(this).data('coords');
- if (coords) {
- self.ias.setSelection.apply(self.ias, coords);
- self.ias.setOptions({ show: true });
- }
- else {
- self._resetSelection();
- }
- });
+ $.themes.autocomplete(self.$tag_name);
old_callback.call(this);
};
$.wiki.Perspective.call(this, options);
};
- MotifsPerspective.prototype = new $.wiki.Perspective();
-
- MotifsPerspective.prototype.freezeState = function(){
-
- };
-
- MotifsPerspective.prototype._resetSelection = function() {
- var self = this;
- self.x1 = self.x2 = self.y1 = self.y2 = null;
- self.ias.setOptions({ hide: true });
- }
-
- MotifsPerspective.prototype._refreshLayout = function() {
- this.$scrolled.css({top: this.$toolbar.height()});
- };
-
- MotifsPerspective.prototype._push = function(name, x1, y1, x2, y2) {
- var $e = $('<span class="image-object"></span>')
- $e.text(name);
- if (x1 !== null)
- $e.data('coords', [x1, y1, x2, y2]);
- this.$objects_list.append($e);
- this.$objects_list.append('<span class="delete">(x) </span>');
- this._refreshLayout();
- }
-
-
- MotifsPerspective.prototype._addObject = function(self) {
- return function() {
- outputs = [];
- chunks = self.$tag_name.val().split(',');
- for (i in chunks) {
- item = chunks[i].trim();
- if (item == '')
- continue;
- outputs.push(item.trim());
- }
- output = outputs.join(', ');
-
- self._push(output, self.x1, self.y1, self.x2, self.y2);
- self._resetSelection();
- }
- }
-
- MotifsPerspective.prototype._fillCoords = function(self) {
- return function(img, selection) {
- $('.active', self.$objects_list).removeClass('active');
- if (selection.x1 != selection.x2 && selection.y1 != selection.y2) {
- self.x1 = selection.x1;
- self.x2 = selection.x2;
- self.y1 = selection.y1;
- self.y2 = selection.y2;
- }
- else {
- self.x1 = self.x2 = self.y1 = self.y2 = null;
- }
- }
- }
-
- MotifsPerspective.prototype.onEnter = function(success, failure){
- var self = this;
- this.$objects_list.children().remove();
-
- $.each(this.doc.getImageItems('theme'), function(i, e) {
- self._push.apply(self, e);
- });
-
- if (this.x1 !== null)
- this.ias.setOptions({enable: true, show: true});
- else
- this.ias.setOptions({enable: true});
-
- $.wiki.Perspective.prototype.onEnter.call(this);
-
- };
-
- MotifsPerspective.prototype.onExit = function(success, failure){
- var self = this;
- var motifs = [];
- this.$objects_list.children(".image-object").each(function(i, e) {
- var args = $(e).data('coords');
- if (!args)
- args = [null, null, null, null];
- args.unshift($(e).text());
- motifs.push(args);
- })
- self.doc.setImageItems('theme', motifs);
-
- this.ias.setOptions({disable: true, hide: true});
-
- };
+ MotifsPerspective.prototype = new $.wiki.ObjectsPerspective();
$.wiki.MotifsPerspective = MotifsPerspective;
(function($){
function ObjectsPerspective(options){
+ if (!options) return;
var old_callback = options.callback;
options.callback = function(){
+ this.$editor = $("#objects-editor");
+ this.object_type_name = "obiekt";
+ this.xml_object_type = 'object';
+ this._init();
+ old_callback.call(this);
+ };
+
+ $.wiki.Perspective.call(this, options);
+ };
+
+ ObjectsPerspective.prototype = new $.wiki.Perspective();
+
+ ObjectsPerspective.prototype._init = function() {
var self = this;
- self.$tag_name = $('#objects-editor .tag-name');
- self.$toolbar = $('#objects-editor .toolbar');
- self.$scrolled = $('#objects-editor .scrolled');
- self.$objects_list = $('#objects-editor .objects-list');
+ self.$tag_name = $('.tag-name', self.$editor);
+ self.$toolbar = $('.toolbar', self.$editor);
+ self.$scrolled = $('.scrolled', self.$editor);
+ self.$objects_list = $('.objects-list', self.$editor);
self.x1 = null;
self.x2 = null;
self.y2 = null;
if (!CurrentDocument.readonly) {
- self.ias = $('#objects-editor img.area-selectable').imgAreaSelect({ handles: true, onSelectEnd: self._fillCoords(self), instance: true });
- $('#objects-editor .add').click(self._addObject(self));
-
- $('.delete', self.$objects_list).live('click', function() {
- $(this).prev().trigger('click');
- if (window.confirm("Czy na pewno chcesz usunąć ten obiekt?")) {
- $(this).prev().remove();
- $(this).remove();
- self._refreshLayout();
+ self.ias = $('img.area-selectable', self.$editor).imgAreaSelect({
+ handles: true,
+ instance: true,
+ onSelectEnd: self._fillCoords(self),
+ onSelectChange: function() {self._cropSelection();},
+ });
+ $('.add', self.$editor).click(self._addObject(self));
+
+ $(self.$objects_list).on('click', '.delete', function() {
+ $(this).parent().trigger('click');
+ if (window.confirm("Czy na pewno chcesz usunąć ten " + object_type_name + "?")) {
+ $(this).parent().remove();
}
self._resetSelection();
return false;
});
}
- $('.image-object', self.$objects_list).live('click', function(){
+ $(self.$objects_list).on('click', '.image-object', function(){
$('.active', self.$objects_list).removeClass('active');
$(this).addClass('active');
var coords = $(this).data('coords');
if (coords) {
self.ias.setSelection.apply(self.ias, coords);
self.ias.setOptions({ show: true });
+ self._cropSelection();
}
else {
self._resetSelection();
}
});
- old_callback.call(this);
- };
-
- $.wiki.Perspective.call(this, options);
- };
-
- ObjectsPerspective.prototype = new $.wiki.Perspective();
-
- ObjectsPerspective.prototype.freezeState = function(){
+ self.$scrolled.scroll(function() {
+ self.ias.update();
+ self._cropSelection();
+ });
+ }
- };
+ ObjectsPerspective.prototype._cropSelection = function() {
+ var mintop = this.$scrolled.offset().top;
+ var maxbottom = mintop + this.$scrolled.height();
+ $(".imgareaselect-outer").each(function(i, e) {
+ var top = parseInt(e.style.top);
+ var height = parseInt(e.style.height);
+ var bottom = top + height;
+ dtop = dheight = 0;
+ if (top < mintop) {
+ dtop += mintop - top;
+ dheight -= dtop;
+ }
+ if (bottom > maxbottom) {
+ dheight -= bottom - maxbottom;
+ }
+ if (dtop) {
+ e.style.top = top + dtop + 'px';
+ }
+ if (dheight) {
+ e.style.height = Math.max(0, height + dheight) + 'px';
+ }
+ });
+ }
ObjectsPerspective.prototype._resetSelection = function() {
var self = this;
self.ias.setOptions({ hide: true });
}
- ObjectsPerspective.prototype._refreshLayout = function() {
- this.$scrolled.css({top: this.$toolbar.height()});
- };
-
ObjectsPerspective.prototype._push = function(name, x1, y1, x2, y2) {
- var $e = $('<span class="image-object"></span>')
- $e.text(name);
+ var $e = $('<span class="image-object btn btn-outline-light mr-1"><span class="name"></span><span class="delete badge badge-danger ml-2">x</span></span>');
+ $(".name", $e).text(name);
if (x1 !== null)
$e.data('coords', [x1, y1, x2, y2]);
this.$objects_list.append($e);
- this.$objects_list.append('<span class="delete">(x) </span>');
- this._refreshLayout();
}
var self = this;
this.$objects_list.children().remove();
- $.each(this.doc.getImageItems('object'), function(i, e) {
+ $.each(this.doc.getImageItems(self.xml_object_type), function(i, e) {
self._push.apply(self, e);
});
var args = $(e).data('coords');
if (!args)
args = [null, null, null, null];
- args.unshift($(e).text());
+ args.unshift($(".name", e).text());
objects.push(args);
})
- self.doc.setImageItems('object', objects);
+ self.doc.setImageItems(self.xml_object_type, objects);
this.ias.setOptions({disable: true, hide: true});
$.wikiapi.WikiDocument = WikiDocument;
})(jQuery);
-
-
-
-// Wykonuje block z załadowanymi kanonicznymi motywami
-function withThemes(code_block, onError)
-{
- if (typeof withThemes.canon == 'undefined') {
- $.ajax({
- url: '/editor/themes',
- dataType: 'text',
- success: function(data) {
- withThemes.canon = data.split('\n');
- code_block(withThemes.canon);
- },
- error: function() {
- withThemes.canon = null;
- code_block(withThemes.canon);
- }
- })
- }
- else {
- code_block(withThemes.canon);
- }
-}
-
<link rel="icon" href="{{ STATIC_URL }}img/pr-icon.png" type="image/png" />
<title>{% block title %}{% block titleextra %}{% endblock titleextra %} ::
{% trans "Platforma Redakcyjna" %}{% endblock title %}</title>
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
{% block extrahead %}
{% endblock %}
</head>
<img src="{{STATIC_URL}}img/spinner.gif" alt="Loading" />
<p>{% trans "Loading" %}</p>
</div>
- </div>
+ </div>
- <div id="body-wrap">
- <!-- version: {{ APP_VERSION }} -->
- <div id="content">{% block maincontent %} {% endblock %}</div>
- </div>
- <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
+ <!-- version: {{ APP_VERSION }} -->
+ {% block maincontent %} {% endblock %}
+ <script
+ src="https://code.jquery.com/jquery-1.9.1.min.js"
+ integrity="sha256-wS9gmOZBqsqWxgIVgA8Y9WcQOa7PgSIX+rPA0VL2rbQ="
+ crossorigin="anonymous"></script>
+ <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
{% block extrabody %}{% endblock %}
</body>
</html>
{% load i18n %}
{% if user.is_authenticated %}
-<span class="user_name">{{ user.username }}</span> |
-
-{% if user.is_staff %}
- <a href="{% url 'admin:index' %}">{% trans "Admin" %}</a> |
-{% endif %}
-
-<a href='{% url "logout" %}{% if logout_to %}?next={{ logout_to }}{% endif %}'>{% trans "Log Out" %}</a>
+ <div class="nav-item dropdown">
+ <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ {{ user.username }}
+ </a>
+ <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
+ {% if user.is_staff %}
+ <a target="_blank" class="dropdown-item" href="{% url 'admin:index' %}">{% trans "Admin" %}</a>
+ {% endif %}
+ <a class="dropdown-item" href='{% url "logout" %}{% if logout_to %}?next={{ logout_to }}{% endif %}'>{% trans "Log Out" %}</a>
+ </div>
+ </div>
{% else %}
-{% url "cas_ng_login" as login_url %}
-{% ifnotequal login_url request.path %}
- <a href='{{ login_url }}'>{% trans "Log In" %}</a>
-{% endifnotequal %}
+ {% url "cas_ng_login" as login_url %}
+ {% if login_url != request.path %}
+ <a class="nav-item nav-link" href='{{ login_url }}'>{% trans "Log In" %}</a>
+ {% endif %}
{% endif %}
class ButtonAdmin(admin.ModelAdmin):
form = ButtonAdminForm
- list_display = ('slug', 'label', 'tooltip', 'accesskey')
+ list_display = ('slug', 'label', 'tooltip', 'accesskey', 'scriptlet')
list_display_links = ('slug',)
list_editable = ('label', 'tooltip', 'accesskey')
prepopulated_fields = {'slug': ('label',)}
{% if button.link %}
-<a href="{{button.link}}" target="_blank">
+<a class="btn btn-outline-secondary" href="{{button.link}}" target="_blank">
{% endif %}
-<button type="button"
+<button type="button" class="btn btn-info btn-sm"
data-ui-action="{{ button.scriptlet_id }}"
data-ui-action-params="{{ button.params|escape }}"
data-ui-accesskey="{{ button.accesskey }}"
</button>
{% if button.link %}
</a>
-{% endif %}
\ No newline at end of file
+{% endif %}
{% load toolbar_tags %}
-<div class="toolbar">
+<div class="navbar navbar-dark bg-secondary toolbar">
+ <div class="mr-auto">
<select class="group_selector">
{% for group in toolbar_groups %}
<option value="{{ group.slug }}" {% if forloop.first %}selected="selected"{% endif %}>{{ group.name }}</option>
{% endfor %}
</select>
- <button type="button" class="prev"><</button>
- <div class="button_group_container">
+ <span class="button_group_container">
{% for group in toolbar_groups %}
- <ul data-group="{{ group.slug }}" class="button_group">
+ <span data-group="{{ group.slug }}" class="button_group">
{# buttons for this group #}
{% for button in group.button_set.all %}
- <li class="toolbar-button">{% toolbar_button button %}</li>
+ {% toolbar_button button %}
{% endfor %}
- </ul>
+ </span>
{% endfor %}
- </div>
- <button type="button" class="next">></button>
+ </span>
+ </div>
</div>
--- /dev/null
+(function($) {
+
+ /*
+ * Basic perspective.
+ */
+ $.wiki.Perspective = function(options) {
+ if(!options) return;
+
+ this.doc = options.doc;
+ if (options.id) {
+ this.perspective_id = options.id;
+ }
+ else {
+ this.perspective_id = '';
+ }
+
+ if(options.callback)
+ options.callback.call(this);
+ };
+
+ $.wiki.Perspective.prototype.config = function() {
+ return $.wiki.state.perspectives[this.perspective_id];
+ }
+
+ $.wiki.Perspective.prototype.toString = function() {
+ return this.perspective_id;
+ };
+
+ $.wiki.Perspective.prototype.dirty = function() {
+ return true;
+ };
+
+ $.wiki.Perspective.prototype.onEnter = function () {
+ // called when perspective in initialized
+ if (!this.noupdate_hash_onenter) {
+ document.location.hash = '#' + this.perspective_id;
+ }
+ };
+
+ $.wiki.Perspective.prototype.onExit = function () {
+ // called when user switches to another perspective
+ if (!this.noupdate_hash_onenter) {
+ document.location.hash = '';
+ }
+ };
+
+ $.wiki.Perspective.prototype.destroy = function() {
+ // pass
+ };
+
+ $.wiki.Perspective.prototype.freezeState = function () {
+ // free UI state (don't store data here)
+ };
+
+ $.wiki.Perspective.prototype.unfreezeState = function (frozenState) {
+ // restore UI state
+ };
+
+ });
+
+ $elem.show();
+ return $elem;
+ };
+
+ /*
--- /dev/null
+(function($) {
+
+
+ SidebarPerspective = function(options) {
+ this.noupdate_hash_onenter = true;
+ $.wiki.Perspective.call(this, options);
+ };
+
+ SidebarPerspective.prototype = new $.wiki.Perspective();
+
+ SidebarPerspective.prototype.onEnter = function(success, failure) {
+ $.wiki.Perspective.prototype.onEnter.call(this);
+
+ $('#vsplitbar').not('.active').trigger('click');
+ $(".vsplitbar-title").text(this.vsplitbar);
+ }
+
+ $.wiki.SidebarPerspective = SidebarPerspective;
+
+
+})(jQuery);
--- /dev/null
+(function($) {
+
+ $.themes = {};
+
+ // Wykonuje block z załadowanymi kanonicznymi motywami
+ $.themes.withCanon = function(code_block, onError) {
+ if (typeof $.themes.canon == 'undefined') {
+ $.ajax({
+ url: '/editor/themes',
+ dataType: 'text',
+ success: function(data) {
+ $.themes.canon = data.split('\n');
+ code_block($.themes.canon);
+ },
+ error: function() {
+ $.themes.canon = null;
+ code_block($.themes.canon);
+ }
+ })
+ }
+ else {
+ code_block($.themes.canon);
+ }
+ };
+
+ function split( val ) {
+ return val.split( /,\s*/ );
+ }
+ function extractLast( term ) {
+ return split( term ).pop();
+ }
+
+ $.themes.autocomplete = function(elem) {
+ elem.autocomplete({
+ source: function(request, response) {
+ var query = extractLast(request.term).toLowerCase();
+ $.themes.withCanon(function(canonThemes) {
+ var candidates = [];
+ $.each(canonThemes, function(i, theme) {
+ if (theme.toLowerCase().startsWith(query)) {
+ candidates.push(theme);
+ }
+ if (candidates.length == 10) {
+ return false;
+ }
+ });
+ response(candidates);
+ });
+ },
+ search: function() {
+ var term = extractLast( this.value );
+ if ( term.length < 1 ) {
+ return false;
+ }
+ },
+ focus: function() {
+ // prevent value inserted on focus
+ return false;
+ },
+ select: function( event, ui ) {
+ var terms = split( this.value );
+ terms.pop();
+ terms.push( ui.item.value );
+ terms.push( "" );
+ this.value = terms.join( ", " );
+ return false;
+ },
+ appendTo: elem.parent()
+ });
+ };
+
+
+})(jQuery);
--- /dev/null
+#splitter {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+
+ #vsplitbar {
+ width: 30px;
+ border-left: 2px solid #999;
+ border-right: 2px solid #999;
+ cursor: pointer;
+ background: #C1C1C1;
+ cursor: col-resize;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ background-color: #E6E6E6;
+ }
+
+ &.active {
+ p {
+ &::before {content: "▼";}
+ &::after {content: "▼";}
+ }
+ }
+
+ p {
+ font-size: 12px;
+ -moz-transform: rotate(270deg);
+ -moz-transform-origin: 50% 50%;
+ -webkit-transform: rotate(270deg);
+ -webkit-transform-origin: 50% 50%;
+ -o-transform: rotate(-270deg);
+ -o-transform-origin: bottom left;
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
+ margin: 0;
+ white-space: nowrap;
+
+ &::before {
+ content: "▲";
+ margin-right: 1em;
+ }
+ &::after {
+ content: "▲";
+ margin-left: 1em;
+ }
+ }
+ }
+}
--- /dev/null
+#html-view {
+ .edit-button {
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
+ font-size: 1rem;
+ line-height: 1.5;
+ }
+}
+
{% extends "wiki/document_details_base.html" %}
{% load i18n %}
+{% load static %}
{% block extrabody %}
-{{ block.super }}
-<script src="{{ STATIC_URL }}js/lib/codemirror-0.8/codemirror.js" type="text/javascript" charset="utf-8">
-</script>
-<script src="{{ STATIC_URL }}js/wiki/loader.js" type="text/javascript" charset="utf-8"> </script>
+ {{ block.super }}
+ <script src="{% static "js/lib/codemirror-5.49.0/codemirror.js" %}" type="text/javascript" charset="utf-8"></script>
+ <script src="{% static "js/lib/codemirror-5.49.0/xml.js" %}" type="text/javascript" charset="utf-8"></script>
+ <script src="{% static 'js/lib/codemirror-5.49.0/searchcursor.js' %}" type="text/javascript" charset="utf-8"></script>
+
+ <link rel="stylesheet" href="{% static "js/lib/codemirror-5.49.0/codemirror.css" %}">
+
+ <script src="{% static "js/wiki/loader.js" %}" type="text/javascript" charset="utf-8"> </script>
{% endblock %}
{% block tabs-menu %}
{% endblock %}
{% block splitter-extra %}
-<div class="vsplitbar" title="{% trans "Click to open/close gallery" %}">
+<div id="vsplitbar" class="vsplitbar" title="{% trans "Click to open/close gallery" %}">
<p class="vsplitbar-title"></p>
</div>
<div id="sidebar">
{% extends "base.html" %}
{% load toolbar_tags i18n %}
+{% load static %}
{% block titleextra %}{% if chunk.pretty_title %}{{ chunk.pretty_title }}{% else %}{{ chunk.book.title }}{% endif %}{% endblock %}
{% block extrahead %}
-{% load pipeline %}
-{% stylesheet 'detail' %}
+ {% load pipeline %}
+ {% stylesheet 'detail' %}
+ <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
{% endblock %}
{% block extrabody %}
-<script type="text/javascript">
- var STATIC_URL = '{{STATIC_URL}}';
-</script>
-{% javascript 'detail' %}
+ <script type="text/javascript">
+ var STATIC_URL = '{{STATIC_URL}}';
+ </script>
+ {% javascript 'detail' %}
+ <script
+ src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"
+ integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU="
+ crossorigin="anonymous"></script>
{% endblock %}
{% block maincontent %}
-<div id="document-meta"
- data-chunk-id="{{ chunk.pk }}" style="display:none">
+ <div id="document-meta"
+ data-chunk-id="{{ chunk.pk }}" style="display:none">
- <span data-key="gallery">{{ chunk.book.gallery }}</span>
- <span data-key="gallery-start">{% if chunk.gallery_start %}{{ chunk.gallery_start }}{% endif %}</span>
- <span data-key="revision">{{ revision }}</span>
+ <span data-key="gallery">{{ chunk.book.gallery }}</span>
+ <span data-key="gallery-start">{% if chunk.gallery_start %}{{ chunk.gallery_start }}{% endif %}</span>
+ <span data-key="revision">{{ revision }}</span>
<span data-key="diff">{{ request.GET.diff }}</span>
- {% block meta-extra %} {% endblock %}
-</div>
+ {% block meta-extra %} {% endblock %}
+ </div>
-<div id="header">
- <h1><a href="{% url 'catalogue_document_list' %}"><img src="{{STATIC_URL}}icons/go-home.png" alt="Home" /></a><a href="{% url 'catalogue_document_list' %}">Strona<br>główna</a></h1>
- <div id="tools">
- <a href="{{ REDMINE_URL }}projects/wl-publikacje/wiki/Pomoc" target="_blank">
+ <nav class="navbar navbar-expand-sm navbar-dark bg-dark" id="header">
+ <a class="navbar-brand" href="{% url 'catalogue_document_list' %}">
+ <img src="{% static 'img/wl-orange.png' %}" alt="Home" />
+ </a>
+
+ <ul class="tabs nav nav-tabs mr-auto" id="tabs">
+ {% block tabs-menu %}{% endblock %}
+ </ul>
+
+ <ul class="tabs nav nav-tabs" id="tabs-right">
+ {% block tabs-right %}{% endblock %}
+ </ul>
+
+ <div class='navbar-nav'>
+ <a class='nav-item nav-link' href="{{ REDMINE_URL }}projects/wl-publikacje/wiki/Pomoc" target="_blank">
{% trans "Help" %}</a>
- | {% include "registration/head_login.html" %}
- | {% trans "Version" %}: <span id="document-revision">{% trans "Unknown" %}</span>
- {% if not readonly %}
- | <button style="margin-left: 6px" id="save-button">{% trans "Save" %}</button>
- <span id='save-attempt-info'>{% trans "Save attempt in progress" %}</span>
- <span id='out-of-date-info'>{% trans "There is a newer version of this document!" %}</span>
- {% endif %}
+ {% include "registration/head_login.html" %}
+ <span class="navbar-text">{% trans "Version" %}: <span id="document-revision">{% trans "Unknown" %}</span></span>
+ {% if not readonly %}
+ <button class='ml-2 nav-item btn btn-primary' id="save-button">{% trans "Save" %}</button>
+ <span id='save-attempt-info' class="navbar-text ml-2"><span class="alert alert-info">{% trans "Save attempt in progress" %}</span></span>
+ <span id='out-of-date-info' class="navbar-text ml-2"><span class="alert alert-danger">{% trans "There is a newer version of this document!" %}</span></span>
+ {% endif %}
</div>
- <ol id="tabs" class="tabs">
- {% block tabs-menu %} {% endblock %}
- </ol>
- <ol id="tabs-right" class="tabs">
- {% block tabs-right %} {% endblock %}
- </ol>
-</div>
-<div id="splitter">
+ </nav>
+ <div id="splitter">
<div id="editor" class="{% block editor-class %} {% endblock %}">
- {% block tabs-content %} {% endblock %}
+ {% block tabs-content %} {% endblock %}
</div>
- {% block splitter-extra %} {% endblock %}
-</div>
+ {% block splitter-extra %} {% endblock %}
+ </div>
-{% block dialogs %} {% endblock %}
+ {% block dialogs %} {% endblock %}
{% endblock %}
{% extends "wiki/document_details_base.html" %}
{% load i18n %}
+{% load static %}
{% block editor-class %}readonly{% endblock %}
{% block extrabody %}
{{ block.super }}
-<script src="{{STATIC_URL}}js/lib/codemirror-0.8/codemirror.js" type="text/javascript" charset="utf-8">
-</script>
-<script src="{{STATIC_URL}}js/wiki/loader_readonly.js" type="text/javascript" charset="utf-8"> </script>
+<script src="{% static "js/lib/codemirror-5.49.0/codemirror.js" %}" type="text/javascript" charset="utf-8"></script>
+<script src="{% static "js/lib/codemirror-5.49.0/xml.js" %}" type="text/javascript" charset="utf-8"></script>
+<link rel="stylesheet" href="{% static "js/lib/codemirror-5.49.0/codemirror.css" %}">
+
+<script src="{% static "js/wiki/loader_readonly.js" %}" type="text/javascript" charset="utf-8"> </script>
{% endblock %}
{% block tabs-menu %}
{% endblock %}
{% block dialogs %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
{% load i18n %}
-<div id="side-annotations">
+<div id="side-annotations" style="display: none">
<!-- annotations toolbar -->
- <div class="toolbar">
- <button class="refresh" title="Przypisy autorskie" data-tag="pa">pa</button>
- <button class="refresh active" title="Przypisy edytorskie" data-tag="pe">pe</button>
- <button class="refresh" title="Przypisy redakcyjne" data-tag="pr">pr</button>
- <button class="refresh" title="Przypisy tłumacza" data-tag="pt">pt</button>
- <button class="refresh" title="Wszystkie przypisy" data-tag="pa,pe,pr,pt">{% trans "all" %}</button>
- <div class="toolbar-end">
+ <ul class="nav nav-tabs mt-1">
+ <li class="nav-item">
+ <a href='#' class="nav-link refresh" title="Przypisy autorskie" data-tag="pa">pa</a>
+ </li>
+ <li class="nav-item">
+ <a href='#' class="nav-link refresh active" title="Przypisy edytorskie" data-tag="pe">pe</a>
+ </li>
+ <li class="nav-item">
+ <a href='#' class="nav-link refresh" title="Przypisy redakcyjne" data-tag="pr">pr</a>
+ </li>
+ <li class="nav-item">
+ <a href='#' class="nav-link refresh" title="Przypisy tłumacza" data-tag="pt">pt</a>
+ </li>
+ <li class="nav-item">
+ <a href='#' class="nav-link refresh" title="Wszystkie przypisy" data-tag="pa,pe,pr,pt">{% trans "all" %}</a>
+ </li>
+ </ul>
+ <div id="annotations-container">
+ <div class="error-message">
+ </div>
+ <div class="annotations-list">
+ </div>
+ <div class="spinner">
+ <div class="spinner-border" role="status">
+ <span class="sr-only">Loading...</span>
</div>
- </div>
- <div class="error-message">
- </div>
- <div class="annotations-list">
- </div>
- <div class="spinner">
- <img src='/media/static/img/spinner.gif' alt="Loading" />
+ </div>
</div>
</div>
{% load i18n %}
-<li id="AnnotationsPerspective" data-ui-related="side-annotations" data-ui-jsclass="AnnotationsPerspective">
- <a href="#"><sup title="{% trans "Annotations" %}">[1]</sup></a>
+<li id="AnnotationsPerspective" data-ui-related="side-annotations" data-ui-jsclass="AnnotationsPerspective" class="nav-item">
+ <a href="#" title="{% trans "Annotations" %}" class="nav-link"><sup>[1]</sup></a>
</li>
{% load i18n %}
<div id="side-gallery">
<!-- gallery toolbar -->
- <div class="toolbar">
- <button class="start-page" alt="{% trans "Go to first image of this part" %}" title="{% trans "Go to first image of this part" %}">
- <img src="{{STATIC_URL}}icons/revert.png"/>
+ <div class="navbar navbar-dark bg-secondary">
+ <div class="nav">
+ <button class="btn btn-info mr-2 start-page" alt="{% trans "Go to first image of this part" %}" title="{% trans "Go to first image of this part" %}">
+ ⇤
</button>
- <button class="previous-page" alt="{% trans "Previous" %}" title="{% trans "Previous" %}">
- <img src="{{STATIC_URL}}icons/go-previous.png"/>
+ <button class="btn btn-info mr-2 previous-page" title="{% trans "Previous" %}">
+ ⯇
</button>
- <input type="text" size="3" maxlength="3" value="0" class="page-number" />
- <span id="imagesCount">/0</span>
- <button class="next-page" alt="{% trans "Next" %}" title="{% trans "Next" %}">
- <img src="{{STATIC_URL}}icons/go-next.png"
- alt="{% trans "Next" %}" title="{% trans "Next" %}"/>
+ <input type="text" size="3" maxlength="3" value="0" class="page-number " />
+ <span class="navbar-text ml-1 mr-2" id="imagesCount">/0</span>
+ <button class="btn btn-info mr-2 next-page" title="{% trans "Next" %}">
+ ⯈
</button>
- <button class="zoom-in">{% trans "Zoom in" %}</button>
- <button class="zoom-out">{% trans "Zoom out" %}</button>
- <div class="toolbar-end">
- </div>
+ <button class="btn btn-info mr-2 zoom-in">+</button>
+ <button class="btn btn-info mr-2 zoom-out">—</button>
</div>
- <div class="error_message">
+ </div>
+ <div class="error_message alert alert-danger ml-4 mr-4 mt-4">
</div>
<div class="gallery-image">
<img src="{{MEDIA_URL}}images/empty.png" alt="no image" />
{% load i18n %}
-<li id="ScanGalleryPerspective" data-ui-related="side-gallery" data-ui-jsclass="ScanGalleryPerspective" class='active'>
- <a href="#"><img src="{{STATIC_URL}}icons/image-x-generic.png" alt="{% trans "Gallery" %}" title="{% trans "Gallery" %}" /></a>
+<li id="ScanGalleryPerspective" data-ui-related="side-gallery" data-ui-jsclass="ScanGalleryPerspective" class='nav-item'>
+ <a href="#" class="nav-link active"><img src="{{STATIC_URL}}icons/image-x-generic.png" alt="{% trans "Gallery" %}" title="{% trans "Gallery" %}" /></a>
</li>
{% load i18n %}
<div id="history-view-editor" class="editor" style="display: none">
- <div class="toolbar">
- <button type="button" id="make-diff-button"
- data-enabled-when="2" disabled="disabled">{% trans "Compare versions" %}</button>
- {% if can_pubmark %}
- <button type="button" id="pubmark-changeset-button"
- data-enabled-when="1" disabled="disabled">{% trans "Mark for publishing" %}</button>
- {% endif %}
- <button type="button" id="doc-revert-button"
- data-enabled-when="1" disabled="disabled">{% trans "Revert document" %}</button>
- <button id="open-preview-button" disabled="disabled"
- data-enabled-when="1"
- data-basehref="{% url 'wiki_editor_readonly' chunk.book.slug chunk.slug %}">{% trans "View version" %}</button>
+ <div class="toolbar navbar navbar-dark bg-secondary">
+ <div class="nav">
+ <button type="button" id="make-diff-button" class="btn btn-info mr-2"
+ data-enabled-when="2" disabled="disabled">{% trans "Compare versions" %}</button>
+ {% if can_pubmark %}
+ <button type="button" id="pubmark-changeset-button" class="btn btn-info mr-2"
+ data-enabled-when="1" disabled="disabled">{% trans "Mark for publishing" %}</button>
+ {% endif %}
+ <button type="button" id="doc-revert-button" class="btn btn-info mr-2"
+ data-enabled-when="1" disabled="disabled">{% trans "Revert document" %}</button>
+ <button id="open-preview-button" disabled="disabled" class="btn btn-info mr-2"
+ data-enabled-when="1"
+ data-basehref="{% url 'wiki_editor_readonly' chunk.book.slug chunk.slug %}">{% trans "View version" %}</button>
- </div>
+ </div>
+ </div>
<div id="history-view">
<p class="message-box" style="display:none;"></p>
{% load i18n %}
-<li id="HistoryPerspective" data-ui-related="history-view-editor" data-ui-jsclass="HistoryPerspective">
- <a href="#">{% trans "History" %}</a>
+<li id="HistoryPerspective" data-ui-related="history-view-editor" data-ui-jsclass="HistoryPerspective" class="nav-item">
+ <a href="#" class="nav-link">{% trans "History" %}</a>
</li>
{% load i18n %}
-<div id='side-search'>
- <p>{% trans "Search" %}:<br/>
- <input id='search-input' /><br/>
- <button id='search-button'>{% trans "Search" %}</button>
- </p>
-
- <p>{% trans "Replace with" %}:<br/>
- <input id='replace-input' /><br/>
- <button id='replace-button'>{% trans "Replace" %}</button><br/>
- <input type="checkbox" id="replace-all" />
- <label for="replace-all">{% trans "Replace all" %}</label><br/>
- </p>
-
- <p>{% trans "Options" %}<br/>
- <input type="checkbox" id="search-case-sensitive" />
- <label for="search-case-sensitive">{% trans "Case sensitive" %}</label><br/>
- <input type="checkbox" id="search-from-cursor" />
- <label for="search-from-cursor">{% trans "From cursor" %}</label><br/>
- </p>
-</div>
\ No newline at end of file
+<div id='side-search' class="container">
+ <div class="form-group">
+ <label for="search-input" class="mt-2">{% trans "Search" %}:</label>
+ <input id='search-input' class="form-control" />
+ </div>
+ <div class="form-group">
+ <button id='search-button' class="btn btn-primary">{% trans "Search" %}</button>
+ </div>
+ <div class="form-group">
+ <label for="replace-input">{% trans "Replace with" %}:</label>
+ <input id='replace-input' class="form-control" />
+ </div>
+ <div class="form-group">
+ <button class="btn btn-primary" id='replace-button'>{% trans "Replace" %}</button>
+ </div>
+ <div class="form-check">
+ <input type="checkbox" id="replace-all" class="form-check-input" />
+ <label for="replace-all">{% trans "Replace all" %}</label>
+ </div>
+ <h6>{% trans "Options" %}</h6>
+ <div class="form-check">
+ <input type="checkbox" id="search-case-sensitive" class="form-check-input" />
+ <label for="search-case-sensitive">{% trans "Case sensitive" %}</label>
+ </div>
+ <div class="form-check">
+ <input type="checkbox" id="search-from-cursor" class="form-check-input" />
+ <label for="search-from-cursor">{% trans "From cursor" %}</label>
+ </div>
+</div>
{% load i18n %}
-<li id="SearchPerspective" data-ui-related="side-search" data-ui-jsclass="SearchPerspective">
+<li id="SearchPerspective" data-ui-related="side-search" data-ui-jsclass="SearchPerspective" class="nav-item">
+ <a href='#' class='nav-link'>
<img src="{{STATIC_URL}}icons/system-search.png" alt="{% trans "Search and replace" %}" title="{% trans "Search and replace" %}" />
+ </a>
</li>
{% load i18n %}
-<li id="CodeMirrorPerspective"
+<li id="CodeMirrorPerspective" class="nav-item"
data-ui-related="source-editor"
data-ui-jsclass="CodeMirrorPerspective">
- <a href="#">{% trans "Source code" %}</a>
-</li>
\ No newline at end of file
+ <a class="nav-link" href="#">{% trans "Source code" %}</a>
+</li>
{% load i18n %}
<div id="summary-view-editor" class="editor" style="display: none">
- <!-- <div class="toolbar">
- </div> -->
<div id="summary-view">
<div class="summary-cover-area">
<p><img id="summary-cover" class="book-cover"
{% else %}
src="{% url 'cover_preview' chunk.book.slug chunk.slug %}"
{% endif %}></p>
- <p><button id="summary-cover-refresh">{% trans "Refresh from working copy" %}</button></p>
+ <p><button class="btn btn-info" id="summary-cover-refresh">{% trans "Refresh from working copy" %}</button></p>
</div>
<h2>
{% load i18n %}
-<li id="SummaryPerspective" data-ui-related="summary-view-editor" data-ui-jsclass="SummaryPerspective">
- <a href="#">{% trans "Summary" %}</a>
+<li id="SummaryPerspective" data-ui-related="summary-view-editor" data-ui-jsclass="SummaryPerspective" class="nav-item">
+ <a class="nav-link" href="#">{% trans "Summary" %}</a>
</li>
{% load i18n %}
<div id="simple-editor" class="editor" style="display: none">
- <div id="html-view" class="htmlview">
- </div>
-
- <div class="toolbar">
- {% if not readonly %}
- <button id="insert-theme-button">
- {% trans "Insert theme" %}
+ <div class="navbar navbar-dark bg-secondary">
+ <div class="nav">
+ {% if not readonly %}
+ <button class="btn btn-info mr-2" id="insert-theme-button">
+ {% trans "Insert theme" %}
</button>
- <button id="insert-annotation-button">
- {% trans "Insert annotation" %}
+ <button class="btn btn-info" id="insert-annotation-button">
+ {% trans "Insert annotation" %}
</button>
- {% endif %}
- <div class="toolbar-end">
- </div>
+ {% endif %}
</div>
+ </div>
+ <div id="html-view" class="htmlview">
+ </div>
</div>
{% load i18n %}
-<li id="VisualPerspective" data-ui-related="simple-editor" data-ui-jsclass="VisualPerspective">
- <a href="#">{% trans "Visual editor" %}</a>
+<li id="VisualPerspective" data-ui-related="simple-editor" data-ui-jsclass="VisualPerspective" class="nav-item">
+ <a href="#" class="nav-link">{% trans "Visual editor" %}</a>
</li>
{% extends "wiki_img/document_details_base.html" %}
{% load i18n %}
+{% load static %}
{% block extrabody %}
-{{ block.super }}
-<script src="{{ STATIC_URL }}js/lib/codemirror-0.8/codemirror.js" type="text/javascript" charset="utf-8">
-</script>
-<script src="{{ STATIC_URL }}js/wiki_img/loader.js" type="text/javascript" charset="utf-8"> </script>
+ {{ block.super }}
+ <script src="{% static "js/lib/codemirror-5.49.0/codemirror.js" %}" type="text/javascript" charset="utf-8"></script>
+ <script src="{% static "js/lib/codemirror-5.49.0/xml.js" %}" type="text/javascript" charset="utf-8"></script>
+
+ <link rel="stylesheet" href="{% static "js/lib/codemirror-5.49.0/codemirror.css" %}">
+
+ <script src="{% static "js/wiki_img/loader.js" %}" type="text/javascript" charset="utf-8"> </script>
{% endblock %}
{% block tabs-menu %}
- {#% include "wiki_img/tabs/summary_view_item.html" %#}
{% include "wiki_img/tabs/motifs_editor_item.html" %}
{% include "wiki_img/tabs/objects_editor_item.html" %}
{% include "wiki_img/tabs/source_editor_item.html" %}
{% endblock %}
{% block tabs-content %}
- {#% include "wiki_img/tabs/summary_view.html" %#}
{% include "wiki_img/tabs/motifs_editor.html" %}
{% include "wiki_img/tabs/objects_editor.html" %}
{% include "wiki_img/tabs/source_editor.html" %}
{% include "wiki/pubmark_dialog.html" %}
{% endif %}
{% endblock %}
-
-{% block editor-class %}
- sideless
-{% endblock %}
-
{% extends "base.html" %}
{% load toolbar_tags i18n %}
+{% load static %}
{% block title %}{{ document.title }} - {{ block.super }}{% endblock %}
{% block extrahead %}
{% load pipeline %}
{% stylesheet 'detail' %}
+<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
{% endblock %}
{% block extrabody %}
var STATIC_URL = '{{STATIC_URL}}';
</script>
{% javascript 'wiki_img' %}
+<script
+ src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"
+ integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU="
+ crossorigin="anonymous"></script>
{% endblock %}
{% block maincontent %}
-<div id="document-meta"
+ <div id="document-meta"
data-object-id="{{ document.pk }}" style="display:none">
<span data-key="revision">{{ revision }}</span>
<span data-key="diff">{{ request.GET.diff }}</span>
{% block meta-extra %} {% endblock %}
-</div>
+ </div>
-<div id="header">
- <h1><a href="{% url 'catalogue_document_list' %}"><img alt="Home" src="{{STATIC_URL}}icons/go-home.png"/></a><a href="{% url 'catalogue_document_list' %}">Strona<br/>główna</a></h1>
- <div id="tools">
- <a href="{{ REDMINE_URL }}projects/wl-publikacje/wiki/Pomoc" target="_blank">
+ <nav class="navbar navbar-expand-sm navbar-dark bg-dark" id="header">
+ <a class="navbar-brand" href="{% url 'catalogue_document_list' %}">
+ <img src="{% static 'img/wl-orange.png' %}" alt="Home" />
+ </a>
+
+ <ul class="tabs nav nav-tabs mr-auto" id="tabs">
+ {% block tabs-menu %}{% endblock %}
+ </ul>
+
+ <ul class="tabs nav nav-tabs" id="tabs-right">
+ {% block tabs-right %}{% endblock %}
+ </ul>
+
+ <div class='navbar-nav'>
+ <a class='nav-item nav-link' href="{{ REDMINE_URL }}projects/wl-publikacje/wiki/Pomoc" target="_blank">
{% trans "Help" %}</a>
- | {% include "registration/head_login.html" %}
- | {% trans "Version" %}: <span id="document-revision">{% trans "Unknown" %}</span>
- {% if not readonly %}
- | <button style="margin-left: 6px" id="save-button">{% trans "Save" %}</button>
- <span id='save-attempt-info'>{% trans "Save attempt in progress" %}</span>
- <span id='out-of-date-info'>{% trans "There is a newer version of this document!" %}</span>
- {% endif %}
+ {% include "registration/head_login.html" %}
+ <span class="navbar-text">{% trans "Version" %}: <span id="document-revision">{% trans "Unknown" %}</span></span>
+ {% if not readonly %}
+ <button class='ml-2 nav-item btn btn-primary' id="save-button">{% trans "Save" %}</button>
+ <span id='save-attempt-info' class="navbar-text ml-2"><span class="alert alert-info">{% trans "Save attempt in progress" %}</span></span>
+ <span id='out-of-date-info' class="navbar-text ml-2"><span class="alert alert-danger">{% trans "There is a newer version of this document!" %}</span></span>
+ {% endif %}
</div>
- <ol id="tabs" class="tabs">
- {% block tabs-menu %} {% endblock %}
- </ol>
-</div>
-<div id="splitter">
+ </nav>
+ <div id="splitter">
<div id="editor" class="{% block editor-class %} {% endblock %}">
- {% block tabs-content %} {% endblock %}
+ {% block tabs-content %} {% endblock %}
</div>
-</div>
+ </div>
-{% block dialogs %} {% endblock %}
+ {% block dialogs %} {% endblock %}
{% endblock %}
{% extends "wiki_img/document_details_base.html" %}
{% load i18n %}
+{% load static %}
{% block extrabody %}
{{ block.super }}
-<script src="{{ STATIC_URL }}js/lib/codemirror-0.8/codemirror.js" type="text/javascript" charset="utf-8">
-</script>
+<script src="{% static "js/lib/codemirror-5.49.0/codemirror.js" %}" type="text/javascript" charset="utf-8"></script>
+<script src="{% static "js/lib/codemirror-5.49.0/xml.js" %}" type="text/javascript" charset="utf-8"></script>
+
+<link rel="stylesheet" href="{% static "js/lib/codemirror-5.49.0/codemirror.css" %}">
+
<script src="{{ STATIC_URL }}js/wiki_img/loader_readonly.js" type="text/javascript" charset="utf-8"> </script>
{% endblock %}
{% include "wiki_img/tabs/objects_editor.html" %}
{% include "wiki_img/tabs/source_editor.html" %}
{% endblock %}
-
-{% block editor-class %}
- sideless
-{% endblock %}
-
{% load i18n %}
<div id="history-view-editor" class="editor" style="display: none">
- <div class="toolbar">
- <button type="button" id="make-diff-button"
- data-enabled-when="2" disabled="disabled">{% trans "Compare versions" %}</button>
- {% if can_pubmark %}
- <button type="button" id="pubmark-changeset-button"
- data-enabled-when="1" disabled="disabled">{% trans "Mark for publishing" %}</button>
- {% endif %}
- <button type="button" id="doc-revert-button"
- data-enabled-when="1" disabled="disabled">{% trans "Revert document" %}</button>
- <button id="open-preview-button" disabled="disabled"
- data-enabled-when="1"
- data-basehref="{% url 'wiki_img_editor_readonly' document.slug %}">{% trans "View version" %}</button>
-
- </div>
+ <div class="toolbar navbar navbar-dark bg-secondary">
+ <div class="nav">
+ <button type="button" id="make-diff-button" class="btn btn-info mr-2"
+ data-enabled-when="2" disabled="disabled">{% trans "Compare versions" %}</button>
+ {% if can_pubmark %}
+ <button type="button" id="pubmark-changeset-button" class="btn btn-info mr-2"
+ data-enabled-when="1" disabled="disabled">{% trans "Mark for publishing" %}</button>
+ {% endif %}
+ <button type="button" id="doc-revert-button" class="btn btn-info mr-2"
+ data-enabled-when="1" disabled="disabled">{% trans "Revert document" %}</button>
+ <button id="open-preview-button" disabled="disabled" class="btn btn-info mr-2"
+ data-enabled-when="1"
+ data-basehref="{% url 'wiki_img_editor_readonly' document.slug %}">{% trans "View version" %}</button>
+ </div>
+ </div>
<div id="history-view">
<p class="message-box" style="display:none;"></p>
{% load i18n %}
<div id="motifs-editor" class="editor" style="display: none">
- <div class="toolbar">
- <input class='tag-name' title='{% trans "Motifs" %}' />
- <button class='add'>{% trans "Add" %}</button>
+ <div class="toolbar navbar navbar-dark bg-secondary">
+ <form class="form-inline">
+ <input class='form-control mr-2 tag-name' title='{% trans "Motifs" %}' />
+ <button class='add btn btn-info mr-2'>{% trans "Add" %}</button>
+ </form>
- <span class="objects-list">
- </span>
-
- <div class="toolbar-end">
- </div>
+ <div class="mr-auto">
+ <span class="objects-list">
+ </span>
</div>
+ </div>
- <div class='scrolled'>
- <img src="{{ document.image.url }}" class='area-selectable' alt="Tagged image" />
- </div>
+ <div class='scrolled'>
+ <img src="{{ document.image.url }}" class='area-selectable' alt="Tagged image" />
+ </div>
</div>
{% load i18n %}
-<li id="MotifsPerspective" data-ui-related="motifs-editor" data-ui-jsclass="MotifsPerspective">
- <a href="#">{% trans "Motifs" %}</a>
+<li id="MotifsPerspective" data-ui-related="motifs-editor" data-ui-jsclass="MotifsPerspective" class="nav-item">
+ <a class="nav-link" href="#">{% trans "Motifs" %}</a>
</li>
{% load i18n %}
<div id="objects-editor" class="editor" style="display: none">
- <div class="toolbar">
- <input class='tag-name' title='{% trans "Object name" %}' />
- <button class='add'>{% trans "Add" %}</button>
+ <div class="toolbar navbar navbar-dark bg-secondary">
+ <form class="form-inline">
+ <input class='tag-name form-control mr-2' title='{% trans "Object name" %}' />
+ <button class='add btn btn-info mr-2'>{% trans "Add" %}</button>
+ </form>
- <span class="objects-list">
- </span>
-
- <div class="toolbar-end">
- </div>
+ <div class="mr-auto">
+ <span class="objects-list">
+ </span>
</div>
+ </div>
- <div class='scrolled'>
- <img src="{{ document.image.url }}" class='area-selectable' alt="Tagged image" />
- </div>
+ <div class='scrolled'>
+ <img src="{{ document.image.url }}" class='area-selectable' alt="Tagged image" />
+ </div>
</div>
{% load i18n %}
-<li id="ObjectsPerspective" data-ui-related="objects-editor" data-ui-jsclass="ObjectsPerspective">
- <a href="#">{% trans "Objects" %}</a>
+<li id="ObjectsPerspective" data-ui-related="objects-editor" data-ui-jsclass="ObjectsPerspective" class="nav-item">
+ <a class="nav-link" href="#">{% trans "Objects" %}</a>
</li>
{% load i18n %}
<li id="CodeMirrorPerspective"
data-ui-related="source-editor"
- data-ui-jsclass="CodeMirrorPerspective">
- <a href="#">{% trans "Source code" %}</a>
-</li>
\ No newline at end of file
+ data-ui-jsclass="CodeMirrorPerspective" class="nav-item">
+ <a href="#" class="nav-link">{% trans "Source code" %}</a>
+</li>