From ee38655cd5c8cd4c6c2a534e243c448f698e15d1 Mon Sep 17 00:00:00 2001 From: Pouria Hadjibagheri Date: Mon, 10 Jul 2017 11:26:11 +0100 Subject: [PATCH] Updated/completed JavaScript docs. --- docs-src/conf.py | 1 + docs-src/js/docs/markdownx.rst | 233 ++++++++-- docs-src/js/docs/utils.rst | 183 +++++++- docs/_modules/index.html | 6 +- docs/_modules/markdownx/exceptions.html | 289 ------------ docs/_modules/markdownx/forms.html | 421 ------------------ docs/_modules/markdownx/utils.html | 413 ----------------- docs/_modules/markdownx/views.html | 312 ------------- docs/_sources/customization.rst.txt | 13 +- docs/_sources/js/docs/markdownx.rst.txt | 233 ++++++++-- docs/_sources/js/docs/utils.rst.txt | 183 +++++++- docs/contribution.html | 6 +- docs/customization.html | 15 +- docs/doctrees/customization.doctree | Bin 69179 -> 70578 bytes docs/doctrees/environment.pickle | Bin 56716 -> 40493 bytes docs/doctrees/index.doctree | Bin 18809 -> 18593 bytes docs/doctrees/js/docs/markdownx.doctree | Bin 29292 -> 74840 bytes docs/doctrees/js/docs/utils.doctree | Bin 12126 -> 57446 bytes docs/doctrees/markdownx/docs/admins.doctree | Bin 5800 -> 5111 bytes .../markdownx/docs/exceptions.doctree | Bin 19031 -> 16595 bytes docs/doctrees/markdownx/docs/fields.doctree | Bin 5130 -> 4387 bytes docs/doctrees/markdownx/docs/forms.doctree | Bin 18759 -> 16336 bytes docs/doctrees/markdownx/docs/models.doctree | Bin 22979 -> 21680 bytes .../doctrees/markdownx/docs/utilities.doctree | Bin 17873 -> 15909 bytes docs/doctrees/markdownx/docs/views.doctree | Bin 21822 -> 18839 bytes docs/example.html | 6 +- docs/genindex.html | 150 ++++++- docs/getting_started.html | 6 +- docs/index.html | 6 +- docs/installation.html | 6 +- docs/js/docs.html | 6 +- docs/js/docs/markdownx.html | 412 +++++++++++++++-- docs/js/docs/utils.html | 352 ++++++++++++++- docs/js/events.html | 6 +- docs/js/js.html | 6 +- docs/license.html | 6 +- docs/markdownx/docs/admins.html | 8 +- docs/markdownx/docs/exceptions.html | 14 +- docs/markdownx/docs/fields.html | 8 +- docs/markdownx/docs/forms.html | 14 +- docs/markdownx/docs/models.html | 10 +- docs/markdownx/docs/utilities.html | 12 +- docs/markdownx/docs/views.html | 16 +- docs/markdownx/markdownx.html | 6 +- docs/objects.inv | Bin 1070 -> 2057 bytes docs/py-modindex.html | 6 +- docs/search.html | 6 +- docs/searchindex.js | 2 +- docs/translation.html | 6 +- 49 files changed, 1716 insertions(+), 1662 deletions(-) delete mode 100644 docs/_modules/markdownx/exceptions.html delete mode 100644 docs/_modules/markdownx/forms.html delete mode 100644 docs/_modules/markdownx/utils.html delete mode 100644 docs/_modules/markdownx/views.html diff --git a/docs-src/conf.py b/docs-src/conf.py index c800af1..4a3860a 100644 --- a/docs-src/conf.py +++ b/docs-src/conf.py @@ -38,6 +38,7 @@ extensions = [ 'sphinx.ext.githubpages', 'classycode', 'sphinxcontrib.autoanysrc', + 'sphinx.ext.autosectionlabel' # 'edit_on_github' ] diff --git a/docs-src/js/docs/markdownx.rst b/docs-src/js/docs/markdownx.rst index ab66d44..4372aaf 100644 --- a/docs-src/js/docs/markdownx.rst +++ b/docs-src/js/docs/markdownx.rst @@ -1,98 +1,237 @@ MarkdownX --------- +.. js:data:: GeneralEventHandlers + + :JSON Object: General (document level) event handlers. + + .. js:function:: inhibitDefault(event) + + Routine tasks for event handlers (e.g. default preventions). + + :param Event|KeyboardEvent event: The events to be inhibited. + :returns: event + :return type: Event + + .. js:function:: onDragEnter (event) + + Upon enter (when a file, e.g. an image, is dragged into the browser), + the defualt events are inhibited, and the drop event is set as `copy`. + + :param DragEvent event: Drag event. + :returns: Inhibited drag event with `copy` added to the drop event. + :return type: Event + + +.. js:data:: keyboardEvents + + :JSON Object: Keyboard events and their handlers. + + .. js:attribute:: TAB + + - (*string*) - represents: ``Tab`` + + .. js:attribute:: DUPLICATE + + - (*string*) - represents: ``d`` + + .. js:attribute:: UNINDENT + + - (*string*) - represents: ``[`` + + .. js:attribute:: INDENT + + - (*string*) - represents: ``]`` + + .. js:function:: hub(event) + + Mapping of hotkeys from keyboard events to their corresponding functions. + + :param KeyboardEvent event: Keyboard event to be handled. + :return type: Function | Boolean + + .. js:data:: Handlers + + Handler functions, as mapped by :js:func:`hub`. + + :JSON Object: Hotkey response functions. + + .. note:: + Properties receive a single argument ``properties``, which is an instance of :js:data:`properties`. See the + ``markdownx`` class for additional details. + + .. js:function:: applyTab(properties) + + Smart application of tab indentations under various conditions. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: removeTab(properties) + + Smart removal of tab indentations. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: _multiLineIndentation(properties) + + **private** + + Handles multi line indentations. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: applyIndentation(properties) + + Smart application of indentation at the beginning of the line. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: removeIndentation(properties) + + Smart removal of indentation from the beginning of the line. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: applyDuplication(properties) + + Duplication of the current or selected lines. + + :param JSON properties: See object descriptions for details. + :return type: string + + +.. js:function:: getHeight(element) + + Returns either the height of an element as defined in the ``style`` attribute or CSS or its browser-computed height. + + :param element HTMLElement: The element whose height is to be determined. + :returns: Height of the element. + :return type: number + + +.. js:function:: updateHeight(element) + + Updates the height of an element based on its scroll height. + + :param HTMLTextAreaElement editor: Editor element whose height is to be updated. + :return type: HTMLTextAreaElement + .. js:class:: MarkdownX(editor, preview) - **Example:** + MarkdownX initializer. - .. code-block:: javascript + :example: + .. code-block:: javascript - let editor = document.getElementById('MyMarkdownEditor'), - preview = document.getElementById('MyMarkdownPreview'); + >>> let parent = document.getElementsByClassName('markdownx'), + ... editor = parent.querySelector('.MyMarkdownEditor'), + ... preview = parent.querySelector('.MyMarkdownPreview'); - let mdx = new MarkdownX(editor, preview) + >>> let mdx = new MarkdownX(parent, editor, preview) - :param HTMLTextAreaElement editor: Markdown editor element. - :param HTMLElement preview: Markdown preview element. + :param HTMLElement parent: Markdown editor element. + :param HTMLTextAreaElement editor: Markdown editor element. + :param HTMLElement preview: Markdown preview element. - .. js:attribute:: editor + .. js:data:: properties - - (*HTMLTextAreaElement*) - Instance editor. + :JSON Object: Class variables. - .. js:attribute:: preview + .. js:attribute:: editor - - (*HMTLElement*) - Instance preview. + - (*HTMLTextAreaElement*) - Instance editor. - .. js:attribute:: timeout + .. js:attribute:: preview - - (*number* | *null*) - Private property; timeout settings. + - (*HMTLElement*) - Instance preview. - .. js:attribute:: _editorIsResizable + .. js:attribute:: parent - - (*Boolean*) - Private property; ``true`` if instance editor is resizable, otherwise ``false``. + - (*HMTLElement*) - Instance parent. - .. js:function:: _markdownify() + .. js:attribute:: _latency - Private settings for ``timeout``. + **private** - .. js:function:: _routineEventResponse(event) + - (*number* | *null*) - Private property; timeout settings. - Private routine tasks for event handlers (e.g. default preventions). + .. js:attribute:: _editorIsResizable - :param Event event: Event to be handled. + **private** + - (*Boolean*) - Private property; ``true`` if instance editor is resizable, otherwise ``false``. - .. js:function:: getEditorHeight(editor) + .. js:function:: _markdownify() - :param HTMLTextAreaElement editor: Markdown editor element. - :returns: The editor's height in pixels; e.g. ``"150px"``. - :return type: string + **private** - .. js:function:: inputChanged() + Settings for ``timeout``. - Event handlers in response to alterations in the instance editor. + .. js:function:: _routineEventResponse(event) - :param Event event: Event to be handled. + **private** + Routine tasks for event handlers (e.g. default preventions). - .. js:function:: onDragEnter(event) + :param Event event: Event to be handled. - Event handler for :guilabel:`dragEnter` events. + .. js:function:: getEditorHeight(editor) - :param Event event: Event to be handled. + :param HTMLTextAreaElement editor: Markdown editor element. + :returns: The editor's height in pixels; e.g. ``"150px"``. + :return type: string + .. js:function:: inputChanged() - .. js:function:: onDragLeave(event) + Event handlers in response to alterations in the instance editor. - Event handler for :guilabel:`dragLeave` events. + :param Event event: Event to be handled. - :param Event event: Event to be handled. + .. js:function:: onDragEnter(event) + Event handler for :guilabel:`dragEnter` events. - .. js:function:: onDrop(event) + :param Event event: Event to be handled. - Event handler for :guilabel:`drop` events (in drag and drops). + .. js:function:: onDragLeave(event) - :param Event event: Event to be handled. + Event handler for :guilabel:`dragLeave` events. + :param Event event: Event to be handled. - .. js:function:: onKeyDown(event) + .. js:function:: onDrop(event) - Event handler for :guilabel:`keyDown` events as registered in the instance editor. + Event handler for :guilabel:`drop` events (in drag and drops). - :param Event event: Event to be handled. - :returns: ``null`` if the key pressed is *Tab* (ASCII #9) else ``false``. - :return type: boolean | null + :param Event event: Event to be handled. - .. js:function:: sendFile() + .. js:function:: onKeyDown(event) - pass + Event handler for :guilabel:`keyDown` events as registered in the instance editor. - .. js:function:: getMarkdown() + :param Event event: Event to be handled. + :returns: ``null`` if the key pressed is *Tab* (ASCII #9) else ``false``. + :return type: boolean | null - pass + .. js:function:: sendFile(file) - .. js:function:: insertImage() + Uploading the ``file`` onto the server through an AJAX request. - pass + :param File file: File to be uploaded. + + .. js:function:: getMarkdown() + + Uploading the markdown text from :attr:`properties.editor` onto the server through an AJAX request, and upon + receiving the HTML encoded text in response, the response will be displayed in :attr:`properties.preview`. + + .. js:function:: insertImage(textToInsert) + + Inserts markdown encoded image URL into :attr:`properties.editor` where the cursor is located. + + :param string textToInsert: Markdown text (with path to the image) to be inserted into the editor. diff --git a/docs-src/js/docs/utils.rst b/docs-src/js/docs/utils.rst index 0a4e59c..387136f 100644 --- a/docs-src/js/docs/utils.rst +++ b/docs-src/js/docs/utils.rst @@ -18,7 +18,62 @@ Utilities not exist. :param string name: The name of the cookie. - :returns: Value of the cookie with the key ``name`` or ``null``. + :returns: Value of the cookie with the key ``name`` or ``null``. + :return type: string | null + + +.. js:function:: zip(...rows) + + JavaScript implementation of Python's ``zip`` function. + + :param rows: Array or arrays to zipped together. + :returns: Array of zipped items. + :return type: Array[] + + :example: + .. code-block:: javascript + + >>> let zipped = zip([1, 'H'], [5, 'i']); + + >>> zipped.map(row => row.reduce((m, n) => m + n)) + // [6, "Hi"] + + +.. js:function:: mountEvents (...collections) + + Mounts a set of events defined in an object onto the document. + + :param collections: Must be JSON object and follow the structure outlined in the example. + :returns: Listeners + :return type: Event + + :example: + .. code-block:: javascript + + >>> let editorListeners = { + ... object: document.getElementById('my-editor'), + ... listeners: [ + ... { type: "input", capture: true , listener: inputChanged }, + ... { type: "compositionstart", capture: true , listener: onKeyDown } + ... ] + ... }; + + +.. js:function:: triggerEvent(element, type) + + Triggers an existing HTML event manually. + + :param Element element: Element whose event is to be created and triggered. + :param string type: Type of the event to be triggered. + + +.. js:function:: triggerCustomEvent(type, element, args) + + Triggers an already defined custom event manually. + + :param Element|Document element: Element whose event is to be triggered. + :param string type: Type of the event to be triggered. + :param args: Values to be passed as custom argument to ``event.details``. (Default = ``null``) .. js:function:: preparePostData(data) @@ -34,3 +89,129 @@ Utilities :returns: A new instance **FormData** that incorporated the data embedded in ``data`` and the CSRF token in enabled. :return type: FormData + + +.. js:function:: AJAXRequest() + + Determines the supported AJAX requests API in IE6+ browsers. + + :return type: XMLHttpRequest + :throws TypeError: AJAX request is not supported. + + +.. js:class:: Request(url, data) + + An XMLHttpRequest wrapper object to initialize AJAX POST requests. + + :example: + .. code-block:: javascript + + >>> let value = "This is a test value", + postUrl = "https://example.com/"; + + >>> const xhr = new utils.Request( + ... postUrl, // URL + ... preparePostData({content: value}) // Data + ... ); + + >>> xhr.success = response => console.log(response); + + >>> xhr.error = response => console.error(response); + + >>> xhr.progress = event => { + ... + ... if (event.lengthComputable) + ... console.info(`${(event.loaded / event.total) * 100}% uploaded.`) + ... + ... }; + + >>> xhr.send(); + + .. js:attribute:: url + + - (*string*) - URL to which the data is to be posted. + + .. js:attribute:: data + + - (*FormData*) - Data, as an instance of `FromData`, to be posted. + + .. js:attribute:: xhr + + **private** + + - (*any*) - Value obtained automatically by calling :js:func:`AjaxRequest`. + + .. js:function:: constructor(url, data) + + :param string url: URL to which the data is to be posted. + :param FormData data: Data, as an instance of `FromData`, to be posted. + + .. js:function:: progress(event) + + Progress callback. + + :param Event event: The entire event (see the example for additional information on usage). + + .. js:function:: success(response) + + Success callback. + + :param any response: Success values; first available one of ``responseText``, ``responseXML``, or ``response``. + + .. js:function:: error(response) + + Error callback. + + :param any response: Error value: ``responseText``. + + .. js:function:: send() + + Starts the transfer. + + +.. js:function:: addClass(element, ...classNames) + + Given an instance of an element, adds classes to it. + + :param Element element: Instance of an element. + :param string[] classNames: Can be a single string, or multiple strings. + + :example: + .. code-block:: javascript + + >>> addClass(document.getElementById('my-element'), 'className'); + + // or + + >>> addClass(document.getElementById('my-element'), 'classA', 'classB', 'classC'); + + +.. js:function:: hasClass(element, className) + + Given an instance of an element, confirms whether or not the element has the class. + + :param Element element: Instance of an element. + :param string[] className: Can be a single string, or multiple strings. + :returns: ``true`` if the element has the class, otherwise ``false``. + :return type: boolean + :example: + .. code-block:: javascript + + >>> hasClass(document.getElementById('my-element'), 'className') + // returns True if the element with id "my-element" has the class "className", otherwise False. + + +.. js:function:: removeClass(element, ...classNames) + + Given an instance of an element, removes classes to it. + + :param Element element: Instance of an element. + :param string[] classNames: Can be a single string, or multiple strings. + :example: + .. code-block:: javascript + + >>> removeClass(document.getElementById('my-element'), 'className'); + + // or + + >>> removeClass(document.getElementById('my-element'), 'classA', 'classB', 'classC'); diff --git a/docs/_modules/index.html b/docs/_modules/index.html index bbccf1d..4a28bd7 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -8,7 +8,7 @@ - Overview: module code — Django Markdownx 2.0.19 documentation + Overview: module code — Django Markdownx 2.0.21 documentation @@ -35,7 +35,7 @@ - + @@ -202,7 +202,7 @@ - - - - - -
- - - - -
- - - - - - -
-
- - - - - - - - - - - - - - - - -
- -
    - -
  • Docs »
  • - -
  • Module code »
  • - -
  • markdownx.exceptions
  • - - -
  • - - - -
  • - -
- - -
-
-
-
- -

Source code for markdownx.exceptions

-from django.utils.translation import ugettext_lazy as _
-from django.forms import ValidationError
-
-
-
[docs]class MarkdownxImageUploadError(ValidationError): - """ - Custom **MarkdownX** exceptions. - """ - - @staticmethod -
[docs] def not_uploaded(): - """ - No file is available to upload. - - :return: Locale compatible version of the error with the following message: - - - No files have been uploaded. - - :rtype: MarkdownxImageUploadError - """ - return MarkdownxImageUploadError(_('No files have been uploaded.'))
- - @staticmethod -
[docs] def unsupported_format(): - """ - The file is of a format not defined in :guilabel:`settings.py` - or if default, in :guilabel:`markdownx/settings.py`. - - :return: Locale compatible version of the error with the following message: - - - File type is not supported. - - :rtype: MarkdownxImageUploadError - """ - return MarkdownxImageUploadError(_('File type is not supported.'))
- - @staticmethod -
[docs] def invalid_size(current, expected): - """ - The file is larger in size that the maximum allow in :guilabel:`settings.py` (or the default). - - :param current: Current size of the file. - :type current: float, int - :param expected: Expected (maximum permitted) size of the file. - :type expected: float, int - :return: Locale compatible version of the error with the following message: - - - Please keep file size under %(max)s. Current file size: %(current)s.' - - :rtype: MarkdownxImageUploadError - """ - from django.template.defaultfilters import filesizeformat - - return MarkdownxImageUploadError( - _('Please keep file size under %(max)s. Current file size: %(current)s.') % { - 'max': filesizeformat(expected), - 'current': filesizeformat(current) - } - )
-
- -
-
- -
-
-
- - -
- -
-

- © Copyright 2017 - Adi, Pouria Hadjibagheri. - -

-
- Built with Sphinx using a theme provided by Read the Docs. - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_modules/markdownx/forms.html b/docs/_modules/markdownx/forms.html deleted file mode 100644 index 27f6db9..0000000 --- a/docs/_modules/markdownx/forms.html +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - - - - - - markdownx.forms — Django Markdownx 2.0.19 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - - - -
-
- - - - - - - - - - - - - - - - -
- - - - -
-
-
-
- -

Source code for markdownx.forms

-# Python internal library.
-from os import path, SEEK_END
-from uuid import uuid4
-from collections import namedtuple
-
-# Django library.
-from django import forms
-from django.utils.six import BytesIO
-from django.core.files.storage import default_storage
-from django.core.files.uploadedfile import InMemoryUploadedFile
-
-# Internal.
-from .utils import scale_and_crop, xml_has_javascript
-from .exceptions import MarkdownxImageUploadError
-
-from .settings import (
-    MARKDOWNX_IMAGE_MAX_SIZE,
-    MARKDOWNX_MEDIA_PATH,
-    MARKDOWNX_UPLOAD_CONTENT_TYPES,
-    MARKDOWNX_UPLOAD_MAX_SIZE,
-    MARKDOWNX_SVG_JAVASCRIPT_PROTECTION
-)
-
-
-
[docs]class ImageForm(forms.Form): - """ - Used for the handling of images uploaded using the editor through :guilabel:`AJAX`. - """ - - image = forms.FileField() - - # Separately defined as it needs to be - # processed a text file rather than image. - _SVG_TYPE = 'image/svg+xml' - -
[docs] def save(self, commit=True): - """ - Saves the uploaded image in the designated location. - - If image type is not SVG, a byteIO of image content_type is created and - subsequently save; otherwise, the SVG is saved in its existing ``charset`` - as an ``image/svg+xml``. - - *Note*: The dimension of image files (excluding SVG) are set using ``PIL``. - - :param commit: If ``True``, the file is saved to the disk; - otherwise, it is held in the memory. - :type commit: bool - :return: An instance of saved image if ``commit is True``, - else ``namedtuple(path, image)``. - :rtype: bool, namedtuple - """ - image = self.files.get('image') - content_type = image.content_type - file_name = image.name - image_extension = content_type.split('/')[-1].upper() - image_size = getattr(image, '_size') - - if content_type.lower() != self._SVG_TYPE: - # Processing the raster graphic image. - # Note that vector graphics in SVG format - # do not require additional processing and - # may be stored as uploaded. - image = self._process_raster(image, image_extension) - image_size = image.tell() - - # Processed file (or the actual file in the case of SVG) is now - # saved in the memory as a Django object. - uploaded_image = InMemoryUploadedFile( - file=image, - field_name=None, - name=file_name, - content_type=content_type, - size=image_size, - charset=None - ) - - if (content_type.lower() == self._SVG_TYPE - and MARKDOWNX_SVG_JAVASCRIPT_PROTECTION - and xml_has_javascript(uploaded_image.read())): - - raise MarkdownxImageUploadError( - 'Failed security monitoring: SVG file contains JavaScript.' - ) - - return self._save(uploaded_image, file_name, commit)
- - def _save(self, image, file_name, commit): - """ - Final saving process, called internally after processing tasks are complete. - - :param image: Prepared image - :type image: django.core.files.uploadedfile.InMemoryUploadedFile - :param file_name: Name of the file using which the image is to be saved. - :type file_name: str - :param commit: If ``True``, the image is saved onto the disk. - :type commit: bool - :return: URL of the uploaded image ``commit=True``, otherwise a namedtuple - of ``(path, image)`` where ``path`` is the absolute path generated - for saving the file, and ``image`` is the prepared image. - :rtype: str, namedtuple - """ - # Defining a universally unique name for the file - # to be saved on the disk. - unique_file_name = self.get_unique_file_name(file_name) - full_path = path.join(MARKDOWNX_MEDIA_PATH, unique_file_name) - - if commit: - default_storage.save(full_path, image) - return default_storage.url(full_path) - - # If `commit is False`, return the path and in-memory image. - image_data = namedtuple('image_data', ['path', 'image']) - return image_data(path=full_path, image=image) - - @staticmethod - def _process_raster(image, extension): - """ - Processing of raster graphic image using Python Imaging Library (PIL). - - This is where raster graphics are processed to the specifications - as defined in ``settings.py``. - - *Note*: The file needs to be uploaded and saved temporarily in the - memory to enable processing tasks using Python Imaging Library (PIL) - to take place and subsequently retained until written onto the disk. - - :param image: Non-SVG image as processed by Django. - :type image: django.forms.BaseForm.file - :param extension: Image extension (e.g.: png, jpg, gif) - :type extension: str - :return: The image object ready to be written into a file. - :rtype: BytesIO - """ - thumb_io = BytesIO() - preped_image = scale_and_crop(image, **MARKDOWNX_IMAGE_MAX_SIZE) - preped_image.save(thumb_io, extension) - thumb_io.seek(0, SEEK_END) - return thumb_io - - @staticmethod -
[docs] def get_unique_file_name(file_name): - """ - Generates a universally unique ID using Python ``UUID`` and attaches the - extension of file name to it. - - :param file_name: Name of the uploaded file, including the extension. - :type file_name: str - :return: Universally unique ID, ending with the extension extracted - from ``file_name``. - :rtype: str - """ - extension = 1 - extension_dot_index = 1 - - file_name = "{unique_name}.{extension}".format( - unique_name=uuid4(), - extension=path.splitext(file_name)[extension][extension_dot_index:] - ) - return file_name
- -
[docs] def clean(self): - """ - Checks the upload against allowed extensions and maximum size. - - :return: Upload - """ - upload = self.cleaned_data.get('image') - - # ----------------------------------------------- - # See comments in `self._error_templates` for - # additional information on each error. - # ----------------------------------------------- - if not upload: - raise MarkdownxImageUploadError.not_uploaded() - - content_type = upload.content_type - file_size = getattr(upload, '_size') - - if content_type not in MARKDOWNX_UPLOAD_CONTENT_TYPES: - - raise MarkdownxImageUploadError.unsupported_format() - - elif file_size > MARKDOWNX_UPLOAD_MAX_SIZE: - - raise MarkdownxImageUploadError.invalid_size( - current=file_size, - expected=MARKDOWNX_UPLOAD_MAX_SIZE - ) - - return upload
-
- -
-
- -
-
-
- - -
- -
-

- © Copyright 2017 - Adi, Pouria Hadjibagheri. - -

-
- Built with Sphinx using a theme provided by Read the Docs. - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_modules/markdownx/utils.html b/docs/_modules/markdownx/utils.html deleted file mode 100644 index 23baf26..0000000 --- a/docs/_modules/markdownx/utils.html +++ /dev/null @@ -1,413 +0,0 @@ - - - - - - - - - - - markdownx.utils — Django Markdownx 2.0.19 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - - - -
-
- - - - - - - - - - - - - - - - -
- - - - -
-
-
-
- -

Source code for markdownx.utils

-from markdown import markdown
-
-from PIL import Image
-
-from .settings import (
-    MARKDOWNX_MARKDOWN_EXTENSIONS,
-    MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS
-)
-
-
-
[docs]def markdownify(content): - """ - Trans-compiles Markdown text to HTML. - - :param content: Markdown text. - :type content: str - :return: HTML encoded text. - :rtype: str - """ - md = markdown( - text=content, - extensions=MARKDOWNX_MARKDOWN_EXTENSIONS, - extension_configs=MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS - ) - return md
- - -def _crop(im, target_x, target_y): - """ - Crops the image to the given specifications. - - :param im: Instance of the image. - :type im: PIL Image - :param target_x: New x-axis. - :type target_x: int - :param target_y: New y-axis - :type target_y: int - :return: Cropped image. - :rtype: PIL.Image - """ - # Use integer values now. - source_x, source_y = im.size - # Difference between new image size and requested size. - diff_x = int(source_x - min(source_x, target_x)) - diff_y = int(source_y - min(source_y, target_y)) - - if diff_x or diff_y: - # Center cropping (default). - halfdiff_x, halfdiff_y = diff_x // 2, diff_y // 2 - box = [ - halfdiff_x, - halfdiff_y, - min(source_x, int(target_x) + halfdiff_x), - min(source_y, int(target_y) + halfdiff_y) - ] - - # Finally, crop the image! - im = im.crop(box) - return im - - -def _scale(im, x, y): - """ - Scales the image to the given specifications. - - :param im: Instance of the image. - :type im: PIL Image - :param x: x-axis size. - :type x: int - :param y: y-axis size. - :type y: int - :return: Scaled image, re-sampled with anti-aliasing filter. - :rtype: Image - """ - im = im.resize( - (int(x), int(y)), - resample=Image.ANTIALIAS - ) - return im - - -
[docs]def scale_and_crop(image, size, crop=False, upscale=False, quality=None): - """ - Modifies raster graphic images to the specifications. - - :param image: Raster graphic image. - :type image: BytesIO - :param size: New size. - :type size: int - :param crop: Perform cropping or not. - :type crop: bool - :param upscale: Whether or not to upscale the image. - :type upscale: bool - :param quality: Quality of the new image in DPI. - :type quality: int - :return: Raster graphic image modified to the given specifications. - :rtype: BytesIO - """ - # Open image and store format/metadata. - image.open() - im = Image.open(image) - im_format, im_info = im.format, im.info - if quality: - im_info['quality'] = quality - - # Force PIL to load image data. - im.load() - - source_x, source_y = map(float, im.size) - target_x, target_y = map(float, size) - - if crop or not target_x or not target_y: - scale = max(target_x / source_x, target_y / source_y) - else: - scale = min(target_x / source_x, target_y / source_y) - - # Handle one-dimensional targets. - if not target_x: - target_x = source_x * scale - elif not target_y: - target_y = source_y * scale - - if scale < 1.0 or (scale > 1.0 and upscale): - im = _scale(im=im, x=source_x * scale, y=source_y * scale) - - if crop: - im = _crop(im=im, target_x=target_x, target_y=target_y) - - # Close image and replace format/metadata, as PIL blows this away. - im.format, im.info = im_format, im_info - - image.close() - - return im
- - -
[docs]def xml_has_javascript(data): - """ - Checks XML for JavaScript. See "security" in :doc:`customization <../../customization>` for - additional information. - - :param data: Contents to be monitored for JavaScript injection. - :type data: str, bytes - :return: ``True`` if **data** contains JavaScript tag(s), otherwise ``False``. - :rtype: bool - """ - from re import search, IGNORECASE, MULTILINE - - data = str(data, encoding='UTF-8') - print(data) - # ------------------------------------------------ - # Handles JavaScript nodes and stringified nodes. - # ------------------------------------------------ - # Filters against "script" / "if" / "for" within node attributes. - pattern = r'(<\s*\bscript\b.*>.*)|(.*\bif\b\s*\(.?={2,3}.*\))|(.*\bfor\b\s*\(.*\))' - - found = search( - pattern=pattern, - string=data, - flags=IGNORECASE | MULTILINE - ) - - if found is not None: - return True - - # ------------------------------------------------ - # Handles JavaScript injection into attributes - # for element creation. - # ------------------------------------------------ - from xml.etree.ElementTree import fromstring - - parsed_xml = ( - (attribute, value) - for elm in fromstring(data).iter() - for attribute, value in elm.attrib.items() - ) - - for key, val in parsed_xml: - if '"' in val or "'" in val: - return True - - # It is (hopefully) safe. - return False
-
- -
-
- -
-
-
- - -
- -
-

- © Copyright 2017 - Adi, Pouria Hadjibagheri. - -

-
- Built with Sphinx using a theme provided by Read the Docs. - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_modules/markdownx/views.html b/docs/_modules/markdownx/views.html deleted file mode 100644 index 82c18a2..0000000 --- a/docs/_modules/markdownx/views.html +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - - - - - - markdownx.views — Django Markdownx 2.0.19 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - - - -
-
- - - - - - - - - - - - - - - - -
- - - - -
-
-
-
- -

Source code for markdownx.views

-from django.http import HttpResponse, JsonResponse
-from django.utils.module_loading import import_string
-from django.views.generic.edit import View, BaseFormView
-
-from .forms import ImageForm
-from .settings import MARKDOWNX_MARKDOWNIFY_FUNCTION
-
-
-
[docs]class MarkdownifyView(View): - """ - Conversion of Markdown to HTML. - """ - -
[docs] def post(self, request, *args, **kwargs): - """ - Handling of the conversion from Markdown to HTML using the conversion - function in settings under ``MARKDOWNX_MARKDOWNIFY_FUNCTION``. - - :param request: HTTP request. - :param args: Default Django POST arguments. - :param kwargs: Default Django POST keyword arguments. - :return: HTTP response - :rtype: django.http.HttpResponse - """ - markdownify = import_string(MARKDOWNX_MARKDOWNIFY_FUNCTION) - return HttpResponse(markdownify(request.POST['content']))
- - -
[docs]class ImageUploadView(BaseFormView): - """ - Handling requests for uploading images. - """ - - # template_name = "dummy.html" - form_class = ImageForm - success_url = '/' - -
[docs] def form_invalid(self, form): - """ - Handling of invalid form events. - - :param form: Django form instance. - :type form: django.forms.Form - :return: JSON response with the HTTP-400 error message for AJAX requests - and the default response for HTTP requests. - :rtype: django.http.JsonResponse, django.http.HttpResponse - """ - response = super(ImageUploadView, self).form_invalid(form) - - if self.request.is_ajax(): - return JsonResponse(form.errors, status=400) - - return response
- -
[docs] def form_valid(self, form): - """ - If the form is valid, the contents are saved. - - If the **POST** request is AJAX (image uploads), a JSON response will be - produced containing the Markdown encoded image insertion tag with the URL - using which the uploaded image may be accessed. - - JSON response would be as follows: - - .. code-block:: bash - - { image_code: "![](/media/image_directory/123-4e6-ga3.png)" } - - :param form: Django form instance. - :type form: django.forms.Form - :return: JSON encoded Markdown tag for AJAX requests, and an appropriate - response for HTTP requests. - :rtype: django.http.JsonResponse, django.http.HttpResponse - """ - response = super(ImageUploadView, self).form_valid(form) - - if self.request.is_ajax(): - image_path = form.save(commit=True) - image_code = '![]({})'.format(image_path) - return JsonResponse({'image_code': image_code}) - - return response
-
- -
-
- -
-
-
- - -
- -
-

- © Copyright 2017 - Adi, Pouria Hadjibagheri. - -

-
- Built with Sphinx using a theme provided by Read the Docs. - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_sources/customization.rst.txt b/docs/_sources/customization.rst.txt index 73a43f2..1846cc9 100644 --- a/docs/_sources/customization.rst.txt +++ b/docs/_sources/customization.rst.txt @@ -14,8 +14,11 @@ The default widget is as seen `here If you would like to customise this; for instance, using `Bootstrap `_ to implement side-by-side panes (as seen in :doc:`preview animation`), you should override the default template by creating -your own template and saving it under ``markdownx/widget2.html`` (Django 1.11+) or ``markdownx/widget.html`` (Django -1.10 and below) in your project's :guilabel:`TEMPLATE_DIRS`. +your own template and saving it under ``markdownx/widget2.html`` (Django 1.11+), or ``markdownx/widget.html`` (Django +1.10 and below) in your project's :guilabel:`TEMPLATE_DIRS`. + +.. Note:: + In the case of Django 1.11+, you will need to `change the renderer`_ (Django docs) to ``TemplatesSetting``. Here is an example of the contents: @@ -23,7 +26,10 @@ Here is an example of the contents:
+ {{ markdownx_editor }} + + {% include 'django/forms/widgets/textarea.html' %}
@@ -359,4 +365,5 @@ the number of CPUs, the amount memory, and how much you are willing to compromis .. _XSS (Cross-site Scripting) attack: https://www.owasp.org/index.php/Cross-site_Scripting_(XSS) .. _presentation: https://www.owasp.org/images/0/03/Mario_Heiderich_OWASP_Sweden_The_image_that_called_me.pdf .. _documentations: https://docs.djangoproject.com/en/dev/topics/security/#cross-site-scripting-xss-protection -.. _CSRF protection middleware: https://docs.djangoproject.com/en/dev/ref/middleware/#module-django.middleware.csrf \ No newline at end of file +.. _CSRF protection middleware: https://docs.djangoproject.com/en/dev/ref/middleware/#module-django.middleware.csrf +.. _change the renderer: https://docs.djangoproject.com/en/1.11/ref/forms/renderers/#overriding-built-in-widget-templates diff --git a/docs/_sources/js/docs/markdownx.rst.txt b/docs/_sources/js/docs/markdownx.rst.txt index ab66d44..4372aaf 100644 --- a/docs/_sources/js/docs/markdownx.rst.txt +++ b/docs/_sources/js/docs/markdownx.rst.txt @@ -1,98 +1,237 @@ MarkdownX --------- +.. js:data:: GeneralEventHandlers + + :JSON Object: General (document level) event handlers. + + .. js:function:: inhibitDefault(event) + + Routine tasks for event handlers (e.g. default preventions). + + :param Event|KeyboardEvent event: The events to be inhibited. + :returns: event + :return type: Event + + .. js:function:: onDragEnter (event) + + Upon enter (when a file, e.g. an image, is dragged into the browser), + the defualt events are inhibited, and the drop event is set as `copy`. + + :param DragEvent event: Drag event. + :returns: Inhibited drag event with `copy` added to the drop event. + :return type: Event + + +.. js:data:: keyboardEvents + + :JSON Object: Keyboard events and their handlers. + + .. js:attribute:: TAB + + - (*string*) - represents: ``Tab`` + + .. js:attribute:: DUPLICATE + + - (*string*) - represents: ``d`` + + .. js:attribute:: UNINDENT + + - (*string*) - represents: ``[`` + + .. js:attribute:: INDENT + + - (*string*) - represents: ``]`` + + .. js:function:: hub(event) + + Mapping of hotkeys from keyboard events to their corresponding functions. + + :param KeyboardEvent event: Keyboard event to be handled. + :return type: Function | Boolean + + .. js:data:: Handlers + + Handler functions, as mapped by :js:func:`hub`. + + :JSON Object: Hotkey response functions. + + .. note:: + Properties receive a single argument ``properties``, which is an instance of :js:data:`properties`. See the + ``markdownx`` class for additional details. + + .. js:function:: applyTab(properties) + + Smart application of tab indentations under various conditions. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: removeTab(properties) + + Smart removal of tab indentations. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: _multiLineIndentation(properties) + + **private** + + Handles multi line indentations. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: applyIndentation(properties) + + Smart application of indentation at the beginning of the line. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: removeIndentation(properties) + + Smart removal of indentation from the beginning of the line. + + :param JSON properties: See object descriptions for details. + :return type: string + + .. js:function:: applyDuplication(properties) + + Duplication of the current or selected lines. + + :param JSON properties: See object descriptions for details. + :return type: string + + +.. js:function:: getHeight(element) + + Returns either the height of an element as defined in the ``style`` attribute or CSS or its browser-computed height. + + :param element HTMLElement: The element whose height is to be determined. + :returns: Height of the element. + :return type: number + + +.. js:function:: updateHeight(element) + + Updates the height of an element based on its scroll height. + + :param HTMLTextAreaElement editor: Editor element whose height is to be updated. + :return type: HTMLTextAreaElement + .. js:class:: MarkdownX(editor, preview) - **Example:** + MarkdownX initializer. - .. code-block:: javascript + :example: + .. code-block:: javascript - let editor = document.getElementById('MyMarkdownEditor'), - preview = document.getElementById('MyMarkdownPreview'); + >>> let parent = document.getElementsByClassName('markdownx'), + ... editor = parent.querySelector('.MyMarkdownEditor'), + ... preview = parent.querySelector('.MyMarkdownPreview'); - let mdx = new MarkdownX(editor, preview) + >>> let mdx = new MarkdownX(parent, editor, preview) - :param HTMLTextAreaElement editor: Markdown editor element. - :param HTMLElement preview: Markdown preview element. + :param HTMLElement parent: Markdown editor element. + :param HTMLTextAreaElement editor: Markdown editor element. + :param HTMLElement preview: Markdown preview element. - .. js:attribute:: editor + .. js:data:: properties - - (*HTMLTextAreaElement*) - Instance editor. + :JSON Object: Class variables. - .. js:attribute:: preview + .. js:attribute:: editor - - (*HMTLElement*) - Instance preview. + - (*HTMLTextAreaElement*) - Instance editor. - .. js:attribute:: timeout + .. js:attribute:: preview - - (*number* | *null*) - Private property; timeout settings. + - (*HMTLElement*) - Instance preview. - .. js:attribute:: _editorIsResizable + .. js:attribute:: parent - - (*Boolean*) - Private property; ``true`` if instance editor is resizable, otherwise ``false``. + - (*HMTLElement*) - Instance parent. - .. js:function:: _markdownify() + .. js:attribute:: _latency - Private settings for ``timeout``. + **private** - .. js:function:: _routineEventResponse(event) + - (*number* | *null*) - Private property; timeout settings. - Private routine tasks for event handlers (e.g. default preventions). + .. js:attribute:: _editorIsResizable - :param Event event: Event to be handled. + **private** + - (*Boolean*) - Private property; ``true`` if instance editor is resizable, otherwise ``false``. - .. js:function:: getEditorHeight(editor) + .. js:function:: _markdownify() - :param HTMLTextAreaElement editor: Markdown editor element. - :returns: The editor's height in pixels; e.g. ``"150px"``. - :return type: string + **private** - .. js:function:: inputChanged() + Settings for ``timeout``. - Event handlers in response to alterations in the instance editor. + .. js:function:: _routineEventResponse(event) - :param Event event: Event to be handled. + **private** + Routine tasks for event handlers (e.g. default preventions). - .. js:function:: onDragEnter(event) + :param Event event: Event to be handled. - Event handler for :guilabel:`dragEnter` events. + .. js:function:: getEditorHeight(editor) - :param Event event: Event to be handled. + :param HTMLTextAreaElement editor: Markdown editor element. + :returns: The editor's height in pixels; e.g. ``"150px"``. + :return type: string + .. js:function:: inputChanged() - .. js:function:: onDragLeave(event) + Event handlers in response to alterations in the instance editor. - Event handler for :guilabel:`dragLeave` events. + :param Event event: Event to be handled. - :param Event event: Event to be handled. + .. js:function:: onDragEnter(event) + Event handler for :guilabel:`dragEnter` events. - .. js:function:: onDrop(event) + :param Event event: Event to be handled. - Event handler for :guilabel:`drop` events (in drag and drops). + .. js:function:: onDragLeave(event) - :param Event event: Event to be handled. + Event handler for :guilabel:`dragLeave` events. + :param Event event: Event to be handled. - .. js:function:: onKeyDown(event) + .. js:function:: onDrop(event) - Event handler for :guilabel:`keyDown` events as registered in the instance editor. + Event handler for :guilabel:`drop` events (in drag and drops). - :param Event event: Event to be handled. - :returns: ``null`` if the key pressed is *Tab* (ASCII #9) else ``false``. - :return type: boolean | null + :param Event event: Event to be handled. - .. js:function:: sendFile() + .. js:function:: onKeyDown(event) - pass + Event handler for :guilabel:`keyDown` events as registered in the instance editor. - .. js:function:: getMarkdown() + :param Event event: Event to be handled. + :returns: ``null`` if the key pressed is *Tab* (ASCII #9) else ``false``. + :return type: boolean | null - pass + .. js:function:: sendFile(file) - .. js:function:: insertImage() + Uploading the ``file`` onto the server through an AJAX request. - pass + :param File file: File to be uploaded. + + .. js:function:: getMarkdown() + + Uploading the markdown text from :attr:`properties.editor` onto the server through an AJAX request, and upon + receiving the HTML encoded text in response, the response will be displayed in :attr:`properties.preview`. + + .. js:function:: insertImage(textToInsert) + + Inserts markdown encoded image URL into :attr:`properties.editor` where the cursor is located. + + :param string textToInsert: Markdown text (with path to the image) to be inserted into the editor. diff --git a/docs/_sources/js/docs/utils.rst.txt b/docs/_sources/js/docs/utils.rst.txt index 0a4e59c..387136f 100644 --- a/docs/_sources/js/docs/utils.rst.txt +++ b/docs/_sources/js/docs/utils.rst.txt @@ -18,7 +18,62 @@ Utilities not exist. :param string name: The name of the cookie. - :returns: Value of the cookie with the key ``name`` or ``null``. + :returns: Value of the cookie with the key ``name`` or ``null``. + :return type: string | null + + +.. js:function:: zip(...rows) + + JavaScript implementation of Python's ``zip`` function. + + :param rows: Array or arrays to zipped together. + :returns: Array of zipped items. + :return type: Array[] + + :example: + .. code-block:: javascript + + >>> let zipped = zip([1, 'H'], [5, 'i']); + + >>> zipped.map(row => row.reduce((m, n) => m + n)) + // [6, "Hi"] + + +.. js:function:: mountEvents (...collections) + + Mounts a set of events defined in an object onto the document. + + :param collections: Must be JSON object and follow the structure outlined in the example. + :returns: Listeners + :return type: Event + + :example: + .. code-block:: javascript + + >>> let editorListeners = { + ... object: document.getElementById('my-editor'), + ... listeners: [ + ... { type: "input", capture: true , listener: inputChanged }, + ... { type: "compositionstart", capture: true , listener: onKeyDown } + ... ] + ... }; + + +.. js:function:: triggerEvent(element, type) + + Triggers an existing HTML event manually. + + :param Element element: Element whose event is to be created and triggered. + :param string type: Type of the event to be triggered. + + +.. js:function:: triggerCustomEvent(type, element, args) + + Triggers an already defined custom event manually. + + :param Element|Document element: Element whose event is to be triggered. + :param string type: Type of the event to be triggered. + :param args: Values to be passed as custom argument to ``event.details``. (Default = ``null``) .. js:function:: preparePostData(data) @@ -34,3 +89,129 @@ Utilities :returns: A new instance **FormData** that incorporated the data embedded in ``data`` and the CSRF token in enabled. :return type: FormData + + +.. js:function:: AJAXRequest() + + Determines the supported AJAX requests API in IE6+ browsers. + + :return type: XMLHttpRequest + :throws TypeError: AJAX request is not supported. + + +.. js:class:: Request(url, data) + + An XMLHttpRequest wrapper object to initialize AJAX POST requests. + + :example: + .. code-block:: javascript + + >>> let value = "This is a test value", + postUrl = "https://example.com/"; + + >>> const xhr = new utils.Request( + ... postUrl, // URL + ... preparePostData({content: value}) // Data + ... ); + + >>> xhr.success = response => console.log(response); + + >>> xhr.error = response => console.error(response); + + >>> xhr.progress = event => { + ... + ... if (event.lengthComputable) + ... console.info(`${(event.loaded / event.total) * 100}% uploaded.`) + ... + ... }; + + >>> xhr.send(); + + .. js:attribute:: url + + - (*string*) - URL to which the data is to be posted. + + .. js:attribute:: data + + - (*FormData*) - Data, as an instance of `FromData`, to be posted. + + .. js:attribute:: xhr + + **private** + + - (*any*) - Value obtained automatically by calling :js:func:`AjaxRequest`. + + .. js:function:: constructor(url, data) + + :param string url: URL to which the data is to be posted. + :param FormData data: Data, as an instance of `FromData`, to be posted. + + .. js:function:: progress(event) + + Progress callback. + + :param Event event: The entire event (see the example for additional information on usage). + + .. js:function:: success(response) + + Success callback. + + :param any response: Success values; first available one of ``responseText``, ``responseXML``, or ``response``. + + .. js:function:: error(response) + + Error callback. + + :param any response: Error value: ``responseText``. + + .. js:function:: send() + + Starts the transfer. + + +.. js:function:: addClass(element, ...classNames) + + Given an instance of an element, adds classes to it. + + :param Element element: Instance of an element. + :param string[] classNames: Can be a single string, or multiple strings. + + :example: + .. code-block:: javascript + + >>> addClass(document.getElementById('my-element'), 'className'); + + // or + + >>> addClass(document.getElementById('my-element'), 'classA', 'classB', 'classC'); + + +.. js:function:: hasClass(element, className) + + Given an instance of an element, confirms whether or not the element has the class. + + :param Element element: Instance of an element. + :param string[] className: Can be a single string, or multiple strings. + :returns: ``true`` if the element has the class, otherwise ``false``. + :return type: boolean + :example: + .. code-block:: javascript + + >>> hasClass(document.getElementById('my-element'), 'className') + // returns True if the element with id "my-element" has the class "className", otherwise False. + + +.. js:function:: removeClass(element, ...classNames) + + Given an instance of an element, removes classes to it. + + :param Element element: Instance of an element. + :param string[] classNames: Can be a single string, or multiple strings. + :example: + .. code-block:: javascript + + >>> removeClass(document.getElementById('my-element'), 'className'); + + // or + + >>> removeClass(document.getElementById('my-element'), 'classA', 'classB', 'classC'); diff --git a/docs/contribution.html b/docs/contribution.html index faa0776..a859c1f 100644 --- a/docs/contribution.html +++ b/docs/contribution.html @@ -8,7 +8,7 @@ - Contributions — Django Markdownx 2.0.19 documentation + Contributions — Django Markdownx 2.0.21 documentation @@ -35,7 +35,7 @@ - + @@ -413,7 +413,7 @@ whether to save the changes or discard them and hold onto the default.

@@ -164,39 +164,83 @@
_ + | A | C + | D | E | F | G + | H | I + | K | M | N | O | P + | R | S | T | U | X + | Z

_

+

A

+ + + +
+

C

+ +
+ +

D

+ + +
@@ -205,6 +249,10 @@ +
@@ -228,6 +276,8 @@

G

+

H

+ + + +
+

I

+

K

+ + +
+

M

@@ -310,7 +390,7 @@

O

- +
@@ -354,7 +460,13 @@

T

+
@@ -362,19 +474,39 @@

U

+

X

+
+

Z

+ + +
+
@@ -411,7 +543,7 @@ @@ -204,7 +204,7 @@