markdown-xblock 2.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. markdown_xblock/__init__.py +2 -0
  2. markdown_xblock/html.py +335 -0
  3. markdown_xblock/public/plugins/codemirror/LICENSE +21 -0
  4. markdown_xblock/public/plugins/codemirror/codemirror-4.8/addon/dialog/dialog.css +32 -0
  5. markdown_xblock/public/plugins/codemirror/codemirror-4.8/addon/dialog/dialog.js +155 -0
  6. markdown_xblock/public/plugins/codemirror/codemirror-4.8/addon/display/fullscreen.css +6 -0
  7. markdown_xblock/public/plugins/codemirror/codemirror-4.8/addon/display/fullscreen.js +41 -0
  8. markdown_xblock/public/plugins/codemirror/codemirror-4.8/addon/edit/matchbrackets.js +120 -0
  9. markdown_xblock/public/plugins/codemirror/codemirror-4.8/addon/search/search.js +159 -0
  10. markdown_xblock/public/plugins/codemirror/codemirror-4.8/addon/search/searchcursor.js +189 -0
  11. markdown_xblock/public/plugins/codemirror/codemirror-4.8/addon/selection/active-line.js +71 -0
  12. markdown_xblock/public/plugins/codemirror/codemirror-4.8/lib/codemirror.css +319 -0
  13. markdown_xblock/public/plugins/codemirror/codemirror-4.8/lib/codemirror.js +7923 -0
  14. markdown_xblock/public/plugins/codemirror/codemirror-4.8/mode/css/css.js +717 -0
  15. markdown_xblock/public/plugins/codemirror/codemirror-4.8/mode/htmlmixed/htmlmixed.js +121 -0
  16. markdown_xblock/public/plugins/codemirror/codemirror-4.8/mode/javascript/javascript.js +684 -0
  17. markdown_xblock/public/plugins/codemirror/codemirror-4.8/mode/markdown/markdown.js +883 -0
  18. markdown_xblock/public/plugins/codemirror/codemirror-4.8/mode/xml/xml.js +384 -0
  19. markdown_xblock/public/plugins/codemirror/langs/en.js +8 -0
  20. markdown_xblock/public/plugins/codemirror/plugin.min.js +1 -0
  21. markdown_xblock/public/plugins/codemirror/source.html +270 -0
  22. markdown_xblock/public/plugins/codesample/css/prism.css +141 -0
  23. markdown_xblock/public/plugins/codesample/js/prism.js +15 -0
  24. markdown_xblock/public/plugins/codesample/plugin.min.js +1 -0
  25. markdown_xblock/public/plugins/image/plugin.min.js +1 -0
  26. markdown_xblock/public/plugins/link/plugin.min.js +1 -0
  27. markdown_xblock/public/plugins/lists/plugin.min.js +1 -0
  28. markdown_xblock/public/plugins/textcolor/plugin.min.js +1 -0
  29. markdown_xblock/public/skin/content.inline.min.css +1 -0
  30. markdown_xblock/public/skin/content.min.css +2 -0
  31. markdown_xblock/public/skin/fonts/icomoon-small.eot +0 -0
  32. markdown_xblock/public/skin/fonts/icomoon-small.svg +175 -0
  33. markdown_xblock/public/skin/fonts/icomoon-small.ttf +0 -0
  34. markdown_xblock/public/skin/fonts/icomoon-small.woff +0 -0
  35. markdown_xblock/public/skin/fonts/icomoon.eot +0 -0
  36. markdown_xblock/public/skin/fonts/icomoon.svg +153 -0
  37. markdown_xblock/public/skin/fonts/icomoon.ttf +0 -0
  38. markdown_xblock/public/skin/fonts/icomoon.woff +0 -0
  39. markdown_xblock/public/skin/fonts/readme.md +1 -0
  40. markdown_xblock/public/skin/img/anchor.gif +0 -0
  41. markdown_xblock/public/skin/img/loader.gif +0 -0
  42. markdown_xblock/public/skin/img/object.gif +0 -0
  43. markdown_xblock/public/skin/img/trans.gif +0 -0
  44. markdown_xblock/public/skin/img/wline.gif +0 -0
  45. markdown_xblock/public/skin/skin.ie7.min.css +1 -0
  46. markdown_xblock/public/skin/skin.json +51 -0
  47. markdown_xblock/public/skin/skin.min.css +1 -0
  48. markdown_xblock/static/css/html.css +15 -0
  49. markdown_xblock/static/css/pygments.css +61 -0
  50. markdown_xblock/static/html/lms.html +1 -0
  51. markdown_xblock/static/html/studio.html +29 -0
  52. markdown_xblock/static/js/html.js +233 -0
  53. markdown_xblock/static/js/tinymce/themes/modern/theme.min.js +1 -0
  54. markdown_xblock/static/js/tinymce/tinymce.js +26801 -0
  55. markdown_xblock/static/js/tinymce/tinymce.min.js +2 -0
  56. markdown_xblock/translations/README.txt +4 -0
  57. markdown_xblock/utils.py +11 -0
  58. markdown_xblock-2.4.0.dist-info/METADATA +158 -0
  59. markdown_xblock-2.4.0.dist-info/RECORD +83 -0
  60. markdown_xblock-2.4.0.dist-info/WHEEL +5 -0
  61. markdown_xblock-2.4.0.dist-info/entry_points.txt +2 -0
  62. markdown_xblock-2.4.0.dist-info/licenses/LICENSE +671 -0
  63. markdown_xblock-2.4.0.dist-info/top_level.txt +5 -0
  64. releasenotes/notes/add-default-css-for-tables-3a53e02e8a350253.yaml +5 -0
  65. releasenotes/notes/add_public_view-d24763b7941dc2b0.yaml +4 -0
  66. releasenotes/notes/custom-css-classes-7984a9c95147d39c.yaml +5 -0
  67. releasenotes/notes/export-md-to-file-ab1528b4c28cb2f0.yaml +5 -0
  68. releasenotes/notes/fix-course-export-b35c78a22be0c6b5.yaml +6 -0
  69. releasenotes/notes/missing-codemirror-files-d64ccb15d0faf640.yaml +6 -0
  70. releasenotes/notes/pypi-release-3e4d5ceb7b6e252a.yaml +7 -0
  71. releasenotes/notes/python-3.12-support-dac839f8d01ac6b9.yaml +4 -0
  72. releasenotes/notes/refactor-ci-36abda8c15250d06.yaml +6 -0
  73. releasenotes/notes/refactor-export-import-49bbf0e8a364f139.yaml +8 -0
  74. releasenotes/notes/remove_py35-0b265861449b1753.yaml +9 -0
  75. releasenotes/notes/safe_mode_replace_escape-40fdb78ec8aa5eff.yaml +7 -0
  76. releasenotes/notes/show_in_read_only_mode-3af39459d5d56b33.yaml +7 -0
  77. requirements/base.txt +9 -0
  78. requirements/dev.txt +2 -0
  79. requirements/reno.txt +1 -0
  80. requirements/test.txt +9 -0
  81. tests/__init__.py +1 -0
  82. tests/test_basics.py +280 -0
  83. var/.placeholder +2 -0
@@ -0,0 +1,2 @@
1
+ """Markdown XBlock module."""
2
+ from .html import MarkdownXBlock # noqa: F401
@@ -0,0 +1,335 @@
1
+ """This XBlock help creating a secure and easy-to-use HTML blocks in edx-platform."""
2
+
3
+ import logging
4
+ import os
5
+
6
+ import markdown2
7
+ from path import Path as path
8
+ from django.conf import settings as django_settings
9
+ from xblock.core import XBlock
10
+ from xblock.fields import List, Scope, String
11
+ from web_fragments.fragment import Fragment
12
+ from xblock.utils.resources import ResourceLoader
13
+ from xblock.utils.settings import XBlockWithSettingsMixin
14
+ from xblock.utils.studio_editable import StudioEditableXBlockMixin, loader
15
+
16
+ from .utils import _
17
+
18
+ log = logging.getLogger(__name__) # pylint: disable=invalid-name
19
+ xblock_loader = ResourceLoader(__name__) # pylint: disable=invalid-name
20
+
21
+ SETTINGS_KEY = 'markdown'
22
+ DEFAULT_EXTRAS = [
23
+ "code-friendly",
24
+ "fenced-code-blocks",
25
+ "footnotes",
26
+ "tables",
27
+ "use-file-vars"
28
+ ]
29
+ DEFAULT_SETTINGS = {
30
+ "extras": DEFAULT_EXTRAS,
31
+ "safe_mode": 'replace'
32
+ }
33
+
34
+
35
+ def get_xblock_settings():
36
+ """Extract xblock settings."""
37
+ try:
38
+ xblock_settings = django_settings.XBLOCK_SETTINGS
39
+ settings = xblock_settings.get(
40
+ SETTINGS_KEY, DEFAULT_SETTINGS)
41
+ except AttributeError:
42
+ settings = DEFAULT_SETTINGS
43
+
44
+ return settings
45
+
46
+
47
+ @XBlock.wants('settings')
48
+ class MarkdownXBlock(StudioEditableXBlockMixin, XBlockWithSettingsMixin, XBlock):
49
+ """
50
+ This XBlock provides content editing in Markdown and displays it in HTML.
51
+ """
52
+
53
+ display_name = String(
54
+ display_name=_('Display Name'),
55
+ help=_('The display name for this component.'),
56
+ scope=Scope.settings,
57
+ default=_('Markdown')
58
+ )
59
+ classes = List(
60
+ display_name=_('Classes'),
61
+ help=_('JSON list of strings representing custom CSS classes to add to this component'),
62
+ scope=Scope.settings,
63
+ default=[]
64
+ )
65
+ data = String(
66
+ help=_('The Markdown content for this module'),
67
+ default=u'',
68
+ scope=Scope.content
69
+ )
70
+ editor = 'markdown'
71
+ editable_fields = ('display_name', 'classes')
72
+ show_in_read_only_mode = True
73
+
74
+ @XBlock.supports('multi_device')
75
+ def student_view(self, context=None): # pylint: disable=unused-argument
76
+ """
77
+ Return a fragment that contains the html for the student view.
78
+ """
79
+ frag = Fragment()
80
+ frag.content = xblock_loader.render_django_template('static/html/lms.html', {'self': self})
81
+
82
+ frag.add_css_url(self.runtime.local_resource_url(self, 'public/plugins/codesample/css/prism.css'))
83
+ frag.add_javascript_url(self.runtime.local_resource_url(self, 'public/plugins/codesample/js/prism.js'))
84
+
85
+ frag.add_css_url(self.runtime.local_resource_url(self, 'static/css/pygments.css'))
86
+ frag.add_css_url(self.runtime.local_resource_url(self, 'static/css/html.css'))
87
+
88
+ return frag
89
+
90
+ def studio_view(self, context=None): # pylint: disable=unused-argument
91
+ """
92
+ Return a fragment that contains the html for the Studio view.
93
+ """
94
+ frag = Fragment()
95
+ settings_fields = self.get_editable_fields()
96
+ settings_page = loader.render_django_template('templates/studio_edit.html', {'fields': settings_fields})
97
+ context = {
98
+ 'self': self,
99
+ 'settings_page': settings_page,
100
+ }
101
+
102
+ frag.content = xblock_loader.render_django_template('static/html/studio.html', context)
103
+
104
+ self.add_stylesheets(frag)
105
+ self.add_scripts(frag)
106
+
107
+ js_data = {
108
+ 'editor': self.editor,
109
+ 'skin_url': self.runtime.local_resource_url(self, 'public/skin'),
110
+ 'external_plugins': self.get_editor_plugins()
111
+ }
112
+ frag.initialize_js('MarkdownXBlock', js_data)
113
+
114
+ return frag
115
+
116
+ @XBlock.supports("multi_device")
117
+ def public_view(self, context=None):
118
+ """
119
+ Return the student_view when course is set to be public.
120
+ """
121
+ return self.student_view(context)
122
+
123
+ @XBlock.json_handler
124
+ def update_content(self, data, suffix=''): # pylint: disable=unused-argument
125
+ """
126
+ Update the saved HTML data with the new HTML passed in the JSON 'content' field.
127
+ """
128
+ self.data = data['content']
129
+
130
+ return {'content': self.data}
131
+
132
+ @staticmethod
133
+ def workbench_scenarios():
134
+ """A canned scenario for display in the workbench."""
135
+ return [
136
+ ('MarkdownXBlock',
137
+ """<vertical_demo>
138
+ <markdown>
139
+ # This is h1
140
+ ## This is h2
141
+ ```
142
+ This is a code block
143
+ ```
144
+ * This is
145
+ * an unordered
146
+ * list
147
+ This is a regular paragraph
148
+ 1. This is
149
+ 1. an ordered
150
+ 1. list
151
+ *This is italic*
152
+ **This is bold**
153
+ </markdown>
154
+ </vertical_demo>
155
+ """),
156
+ ]
157
+
158
+ def add_stylesheets(self, frag):
159
+ """
160
+ A helper method to add all necessary styles to the fragment.
161
+ :param frag: The fragment that will hold the scripts.
162
+ """
163
+ frag.add_css_url(self.runtime.local_resource_url(self, 'static/css/html.css'))
164
+
165
+ frag.add_css_url(self.runtime.local_resource_url(
166
+ self, 'public/plugins/codemirror/codemirror-4.8/lib/codemirror.css'))
167
+
168
+ def add_scripts(self, frag):
169
+ """
170
+ A helper method to add all necessary scripts to the fragment.
171
+ :param frag: The fragment that will hold the scripts.
172
+ """
173
+ frag.add_javascript_url(self.runtime.local_resource_url(self, 'static/js/tinymce/tinymce.min.js'))
174
+ frag.add_javascript_url(self.runtime.local_resource_url(self, 'static/js/tinymce/themes/modern/theme.min.js'))
175
+ frag.add_javascript_url(self.runtime.local_resource_url(self, 'static/js/html.js'))
176
+ frag.add_javascript(loader.load_unicode('public/studio_edit.js'))
177
+
178
+ code_mirror_dir = 'public/plugins/codemirror/codemirror-4.8/'
179
+
180
+ frag.add_javascript_url(self.runtime.local_resource_url(self, code_mirror_dir + 'lib/codemirror.js'))
181
+ frag.add_javascript_url(self.runtime.local_resource_url(self, code_mirror_dir + 'mode/markdown/markdown.js'))
182
+
183
+ def get_editor_plugins(self):
184
+ """
185
+ This method will generate a list of external plugins urls to be used in TinyMCE editor.
186
+ These plugins should live in `public` directory for us to generate URLs for.
187
+
188
+ const PLUGINS_DIR = "/resource/html5/public/plugins/";
189
+ const EXTERNAL_PLUGINS = PLUGINS.map(function(p) { return PLUGINS_DIR + p + "/plugin.min.js" });
190
+
191
+ :return: A list of URLs
192
+ """
193
+ plugin_path = 'public/plugins/{plugin}/plugin.min.js'
194
+ plugins = ['codesample', 'image', 'link', 'lists', 'textcolor', 'codemirror']
195
+
196
+ return {
197
+ plugin: self.runtime.local_resource_url(self, plugin_path.format(plugin=plugin)) for plugin in plugins
198
+ }
199
+
200
+ def substitute_keywords(self, html):
201
+ """
202
+ Replaces all %%-encoded words using KEYWORD_FUNCTION_MAP mapping functions.
203
+
204
+ Iterates through all keywords that must be substituted and replaces them by calling the corresponding functions
205
+ stored in `keywords`. If the function throws a specified exception, the substitution is not performed.
206
+
207
+ Functions stored in `keywords` must either:
208
+ - return a replacement string
209
+ - throw `KeyError` or `AttributeError`, `TypeError`.
210
+ """
211
+ data = html
212
+ system = getattr(self, 'system', None)
213
+ if not system: # This shouldn't happen, but if `system` is missing, then skip substituting keywords.
214
+ return data
215
+
216
+ keywords = {
217
+ '%%USER_ID%%': lambda: getattr(system, 'anonymous_student_id'),
218
+ '%%COURSE_ID%%': lambda: getattr(system, 'course_id').html_id(),
219
+ }
220
+
221
+ for key, substitutor in keywords.items():
222
+ if key in data:
223
+ try:
224
+ data = data.replace(key, substitutor())
225
+ except (KeyError, AttributeError, TypeError):
226
+ # Do not replace the keyword when substitutor is not present.
227
+ pass
228
+
229
+ return data
230
+
231
+ @property
232
+ def html(self):
233
+ """
234
+ A property that returns the markdown content data as html.
235
+ """
236
+ settings = get_xblock_settings()
237
+ extras = settings.get("extras",
238
+ DEFAULT_SETTINGS['extras'])
239
+ safe_mode = settings.get("safe_mode",
240
+ DEFAULT_SETTINGS['safe_mode'])
241
+
242
+ html = markdown2.markdown(
243
+ self.data,
244
+ extras=extras,
245
+ safe_mode=safe_mode
246
+ )
247
+
248
+ html = self.substitute_keywords(html)
249
+
250
+ return html
251
+
252
+ def get_editable_fields(self):
253
+ """
254
+ This method extracts the editable fields from this XBlock and returns them after validating them.
255
+
256
+ Part of this method's copied from StudioEditableXBlockMixin#submit_studio_edits
257
+ with some modifications..
258
+ :return: A list of the editable fields with the information that
259
+ the template needs to render a form field for them.
260
+
261
+ """
262
+ fields = []
263
+
264
+ # Build a list of all the fields that can be edited:
265
+ for field_name in self.editable_fields:
266
+ field = self.fields[field_name] # pylint: disable=unsubscriptable-object
267
+ assert field.scope in (Scope.content, Scope.settings), (
268
+ 'Only Scope.content or Scope.settings fields can be used with '
269
+ 'StudioEditableXBlockMixin. Other scopes are for user-specific data and are '
270
+ 'not generally created/configured by content authors in Studio.'
271
+ )
272
+ field_info = self._make_field_info(field_name, field)
273
+ if field_info is not None:
274
+ fields.append(field_info)
275
+
276
+ return fields
277
+
278
+ @classmethod
279
+ def parse_xml(cls, node, runtime, keys):
280
+ """
281
+ Use `node` to construct a new block.
282
+ """
283
+ block = runtime.construct_xblock_from_class(cls, keys)
284
+ id_generator = runtime.id_generator
285
+
286
+ # Read markdown content from file and add to editor.
287
+ url_name = node.get('url_name', node.get('slug'))
288
+ location = id_generator.create_definition(node.tag, url_name)
289
+
290
+ filename = node.get('filename')
291
+ pointer_path = "{category}/{url_path}".format(
292
+ category='markdown',
293
+ url_path=location.block_id.replace(':', '/')
294
+ )
295
+ base = path(pointer_path).dirname()
296
+ filepath = u"{base}/{name}.md".format(base=base, name=filename)
297
+
298
+ with runtime.resources_fs.open(filepath, encoding='utf-8') as infile:
299
+ markdown = infile.read()
300
+ block.data = markdown
301
+
302
+ # Attributes become fields.
303
+ for name, value in list(node.items()): # lxml has no iteritems
304
+ cls._set_field_if_present(block, name, value, {})
305
+
306
+ return block
307
+
308
+ def add_xml_to_node(self, node):
309
+ """
310
+ For exporting, set data on etree.Element `node`.
311
+ """
312
+
313
+ # Write markdown data to file
314
+ pathname = self.url_name.replace(':', '/')
315
+ filepath = u'{category}/{pathname}.md'.format(
316
+ category=self.category,
317
+ pathname=pathname
318
+ )
319
+
320
+ self.runtime.export_fs.makedirs(
321
+ os.path.dirname(filepath),
322
+ recreate=True)
323
+
324
+ with self.runtime.export_fs.open(filepath, 'wb') as filestream:
325
+ markdown_data = self.data.encode('utf-8')
326
+ filestream.write(markdown_data)
327
+
328
+ # Write out the markdown file name
329
+ filename = path(pathname).basename()
330
+
331
+ node.tag = self.category
332
+ node.set("filename", filename)
333
+ node.set('xblock-family', self.entry_point)
334
+ node.set('display_name', self.display_name)
335
+ node.set('classes', str(self.classes))
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ .CodeMirror-dialog {
2
+ position: absolute;
3
+ left: 0; right: 0;
4
+ background: white;
5
+ z-index: 15;
6
+ padding: .1em .8em;
7
+ overflow: hidden;
8
+ color: #333;
9
+ }
10
+
11
+ .CodeMirror-dialog-top {
12
+ border-bottom: 1px solid #eee;
13
+ top: 0;
14
+ }
15
+
16
+ .CodeMirror-dialog-bottom {
17
+ border-top: 1px solid #eee;
18
+ bottom: 0;
19
+ }
20
+
21
+ .CodeMirror-dialog input {
22
+ border: none;
23
+ outline: none;
24
+ background: transparent;
25
+ width: 20em;
26
+ color: inherit;
27
+ font-family: monospace;
28
+ }
29
+
30
+ .CodeMirror-dialog button {
31
+ font-size: 70%;
32
+ }
@@ -0,0 +1,155 @@
1
+ // CodeMirror, copyright (c) by Marijn Haverbeke and others
2
+ // Distributed under an MIT license: http://codemirror.net/LICENSE
3
+
4
+ // Open simple dialogs on top of an editor. Relies on dialog.css.
5
+
6
+ (function(mod) {
7
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
8
+ mod(require("../../lib/codemirror"));
9
+ else if (typeof define == "function" && define.amd) // AMD
10
+ define(["../../lib/codemirror"], mod);
11
+ else // Plain browser env
12
+ mod(CodeMirror);
13
+ })(function(CodeMirror) {
14
+ function dialogDiv(cm, template, bottom) {
15
+ var wrap = cm.getWrapperElement();
16
+ var dialog;
17
+ dialog = wrap.appendChild(document.createElement("div"));
18
+ if (bottom)
19
+ dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
20
+ else
21
+ dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
22
+
23
+ if (typeof template == "string") {
24
+ dialog.innerHTML = template;
25
+ } else { // Assuming it's a detached DOM element.
26
+ dialog.appendChild(template);
27
+ }
28
+ return dialog;
29
+ }
30
+
31
+ function closeNotification(cm, newVal) {
32
+ if (cm.state.currentNotificationClose)
33
+ cm.state.currentNotificationClose();
34
+ cm.state.currentNotificationClose = newVal;
35
+ }
36
+
37
+ CodeMirror.defineExtension("openDialog", function(template, callback, options) {
38
+ if (!options) options = {};
39
+
40
+ closeNotification(this, null);
41
+
42
+ var dialog = dialogDiv(this, template, options.bottom);
43
+ var closed = false, me = this;
44
+ function close(newVal) {
45
+ if (typeof newVal == 'string') {
46
+ inp.value = newVal;
47
+ } else {
48
+ if (closed) return;
49
+ closed = true;
50
+ dialog.parentNode.removeChild(dialog);
51
+ me.focus();
52
+
53
+ if (options.onClose) options.onClose(dialog);
54
+ }
55
+ }
56
+
57
+ var inp = dialog.getElementsByTagName("input")[0], button;
58
+ if (inp) {
59
+ if (options.value) {
60
+ inp.value = options.value;
61
+ inp.select();
62
+ }
63
+
64
+ if (options.onInput)
65
+ CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
66
+ if (options.onKeyUp)
67
+ CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
68
+
69
+ CodeMirror.on(inp, "keydown", function(e) {
70
+ if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
71
+ if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
72
+ inp.blur();
73
+ CodeMirror.e_stop(e);
74
+ close();
75
+ }
76
+ if (e.keyCode == 13) callback(inp.value);
77
+ });
78
+
79
+ if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
80
+
81
+ inp.focus();
82
+ } else if (button = dialog.getElementsByTagName("button")[0]) {
83
+ CodeMirror.on(button, "click", function() {
84
+ close();
85
+ me.focus();
86
+ });
87
+
88
+ if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
89
+
90
+ button.focus();
91
+ }
92
+ return close;
93
+ });
94
+
95
+ CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
96
+ closeNotification(this, null);
97
+ var dialog = dialogDiv(this, template, options && options.bottom);
98
+ var buttons = dialog.getElementsByTagName("button");
99
+ var closed = false, me = this, blurring = 1;
100
+ function close() {
101
+ if (closed) return;
102
+ closed = true;
103
+ dialog.parentNode.removeChild(dialog);
104
+ me.focus();
105
+ }
106
+ buttons[0].focus();
107
+ for (var i = 0; i < buttons.length; ++i) {
108
+ var b = buttons[i];
109
+ (function(callback) {
110
+ CodeMirror.on(b, "click", function(e) {
111
+ CodeMirror.e_preventDefault(e);
112
+ close();
113
+ if (callback) callback(me);
114
+ });
115
+ })(callbacks[i]);
116
+ CodeMirror.on(b, "blur", function() {
117
+ --blurring;
118
+ setTimeout(function() { if (blurring <= 0) close(); }, 200);
119
+ });
120
+ CodeMirror.on(b, "focus", function() { ++blurring; });
121
+ }
122
+ });
123
+
124
+ /*
125
+ * openNotification
126
+ * Opens a notification, that can be closed with an optional timer
127
+ * (default 5000ms timer) and always closes on click.
128
+ *
129
+ * If a notification is opened while another is opened, it will close the
130
+ * currently opened one and open the new one immediately.
131
+ */
132
+ CodeMirror.defineExtension("openNotification", function(template, options) {
133
+ closeNotification(this, close);
134
+ var dialog = dialogDiv(this, template, options && options.bottom);
135
+ var closed = false, doneTimer;
136
+ var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
137
+
138
+ function close() {
139
+ if (closed) return;
140
+ closed = true;
141
+ clearTimeout(doneTimer);
142
+ dialog.parentNode.removeChild(dialog);
143
+ }
144
+
145
+ CodeMirror.on(dialog, 'click', function(e) {
146
+ CodeMirror.e_preventDefault(e);
147
+ close();
148
+ });
149
+
150
+ if (duration)
151
+ doneTimer = setTimeout(close, duration);
152
+
153
+ return close;
154
+ });
155
+ });
@@ -0,0 +1,6 @@
1
+ .CodeMirror-fullscreen {
2
+ position: fixed;
3
+ top: 0; left: 0; right: 0; bottom: 0;
4
+ height: auto;
5
+ z-index: 9;
6
+ }
@@ -0,0 +1,41 @@
1
+ // CodeMirror, copyright (c) by Marijn Haverbeke and others
2
+ // Distributed under an MIT license: http://codemirror.net/LICENSE
3
+
4
+ (function(mod) {
5
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
6
+ mod(require("../../lib/codemirror"));
7
+ else if (typeof define == "function" && define.amd) // AMD
8
+ define(["../../lib/codemirror"], mod);
9
+ else // Plain browser env
10
+ mod(CodeMirror);
11
+ })(function(CodeMirror) {
12
+ "use strict";
13
+
14
+ CodeMirror.defineOption("fullScreen", false, function(cm, val, old) {
15
+ if (old == CodeMirror.Init) old = false;
16
+ if (!old == !val) return;
17
+ if (val) setFullscreen(cm);
18
+ else setNormal(cm);
19
+ });
20
+
21
+ function setFullscreen(cm) {
22
+ var wrap = cm.getWrapperElement();
23
+ cm.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset,
24
+ width: wrap.style.width, height: wrap.style.height};
25
+ wrap.style.width = "";
26
+ wrap.style.height = "auto";
27
+ wrap.className += " CodeMirror-fullscreen";
28
+ document.documentElement.style.overflow = "hidden";
29
+ cm.refresh();
30
+ }
31
+
32
+ function setNormal(cm) {
33
+ var wrap = cm.getWrapperElement();
34
+ wrap.className = wrap.className.replace(/\s*CodeMirror-fullscreen\b/, "");
35
+ document.documentElement.style.overflow = "";
36
+ var info = cm.state.fullScreenRestore;
37
+ wrap.style.width = info.width; wrap.style.height = info.height;
38
+ window.scrollTo(info.scrollLeft, info.scrollTop);
39
+ cm.refresh();
40
+ }
41
+ });