├── .editorconfig ├── .flake8 ├── .gitattributes ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── markdownfield ├── __init__.py ├── apps.py ├── forms.py ├── models.py ├── static │ └── markdownfield │ │ ├── easymde │ │ ├── easymde.min.css │ │ └── easymde.min.js │ │ ├── fontawesome │ │ ├── font-awesome.min.css │ │ └── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── md.css │ │ └── md_admin.css ├── templates │ └── markdownfield │ │ └── widget.html ├── util.py ├── validators.py └── widgets.py ├── pyproject.toml └── screenshots └── editor.png /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 4 6 | insert_final_newline = true 7 | 8 | [*.py] 9 | indent_style = space 10 | 11 | [*.{scss, css}] 12 | indent_style = tab 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = 4 | node_modules 5 | .git, 6 | .tox, 7 | .pytest_cache, 8 | *migrations/*, 9 | __pycache__, 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize line endings. 2 | * text=auto 3 | 4 | # Explicitly declare text files. 5 | *.py text diff=python 6 | *.html text diff=html 7 | *.tpl text diff=html 8 | *.css text diff=css 9 | *.scss text diff=css 10 | *.webmanifest text 11 | *.txt text 12 | *.md text 13 | *.json text 14 | *.xml text 15 | *.yaml text 16 | *.svg text 17 | *.yml text 18 | *.ini text 19 | *.js text 20 | 21 | Procfile text 22 | .gitignore text 23 | .gitattributes text 24 | .editorconfig text 25 | 26 | # Explicitly declare binary files. 27 | *.png binary 28 | *.gif binary 29 | *.jpg binary 30 | *.jpeg binary 31 | *.jfif binary 32 | *.ico binary 33 | *.webp binary 34 | *.webm binary 35 | 36 | # Correct Github language graph. 37 | *.tpl linguist-language=HTML+Django 38 | *.html linguist-language=HTML+Django 39 | 40 | # Hide diffs for package-lock. 41 | package-lock.json -diff 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # celery beat schedule file 96 | celerybeat-schedule 97 | 98 | # SageMath parsed files 99 | *.sage.py 100 | 101 | # Environments 102 | .env 103 | .venv 104 | env/ 105 | venv/ 106 | ENV/ 107 | env.bak/ 108 | venv.bak/ 109 | 110 | # Spyder project settings 111 | .spyderproject 112 | .spyproject 113 | 114 | # Rope project settings 115 | .ropeproject 116 | 117 | # mkdocs documentation 118 | /site 119 | 120 | # mypy 121 | .mypy_cache/ 122 | .dmypy.json 123 | dmypy.json 124 | 125 | # Pyre type checker 126 | .pyre/ 127 | 128 | .idea 129 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | default_language_version: 4 | python: python3.11 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v4.4.0 8 | hooks: 9 | - id: trailing-whitespace 10 | - id: end-of-file-fixer 11 | - id: check-yaml 12 | - id: check-json 13 | - id: check-ast 14 | - id: check-merge-conflict 15 | - id: check-builtin-literals 16 | - id: check-added-large-files 17 | args: ['--maxkb=1024'] 18 | 19 | - repo: https://github.com/PyCQA/flake8 20 | rev: 6.1.0 21 | hooks: 22 | - id: flake8 23 | additional_dependencies: [flake8-django, flake8-builtins] 24 | 25 | - repo: https://github.com/PyCQA/isort 26 | rev: 5.12.0 27 | hooks: 28 | - id: isort 29 | additional_dependencies: [toml] 30 | 31 | - repo: https://github.com/asottile/pyupgrade 32 | rev: v3.10.1 33 | hooks: 34 | - id: pyupgrade 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2021 Luke Rogers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-markdownfield [![PyPI](https://img.shields.io/pypi/v/django-markdownfield)](https://pypi.org/project/django-markdownfield/) 2 | A simple custom field for Django that can safely render Markdown and store it in the database. 3 | 4 | Your text is stored in a `MarkdownField`. When the model is saved, django-markdownfield will 5 | parse the Markdown, render it, sanitise it with [bleach](https://github.com/mozilla/bleach), and store 6 | the result in a `RenderedMarkdownField` for display to end users. 7 | 8 | django-markdownfield also bundles a minified version of the [EasyMDE](https://github.com/Ionaru/easy-markdown-editor) 9 | editor (v2.14.0) in admin views to make working with Markdown easier. 10 | 11 | ![alt test](https://raw.githubusercontent.com/dmptrluke/django-markdownfield/master/screenshots/editor.png) 12 | 13 | ## Installation 14 | 15 | django-markdownfield can be installed from PyPi: 16 | 17 | ```console 18 | # Install directly or add to your requirements.txt 19 | pip install django-markdownfield 20 | ``` 21 | 22 | After installation, you need to add `markdownfield` to `INSTALLED_APPS` of your Django project's settings. 23 | 24 | ```python 25 | INSTALLED_APPS = [ 26 | "markdownfield", 27 | ... 28 | "django.contrib.staticfiles", 29 | ] 30 | ``` 31 | 32 | ## Usage 33 | 34 | Implementing django-markdownfield is simple. See the below example. 35 | 36 | 37 | ```python 38 | from django.db import models 39 | 40 | from markdownfield.models import MarkdownField, RenderedMarkdownField 41 | from markdownfield.validators import VALIDATOR_STANDARD 42 | 43 | class Page(models.Model): 44 | text = MarkdownField(rendered_field='text_rendered', validator=VALIDATOR_STANDARD) 45 | text_rendered = RenderedMarkdownField() 46 | ``` 47 | 48 | Please also set `SITE_URL` in your Django configuration - it will be needed for detecting 49 | external links. 50 | 51 | ```python 52 | SITE_URL = "https://example.com" 53 | ``` 54 | 55 | To disable the EasyMDE editor, see the amended line below. 56 | 57 | ```python 58 | text = MarkdownField(rendered_field='text_rendered', use_editor=False, use_admin_editor=True) 59 | ``` 60 | 61 | ### Use in templates 62 | 63 | To use the rendered markdown in templates, just use the `RenderedMarkdownField()` you created on 64 | your model, like below. This field should be marked as safe with the `safe` filter to ensure it 65 | displays correctly. 66 | 67 | ```djangotemplate 68 | {{ post.text_rendered | safe }} 69 | ``` 70 | 71 | ## Validators 72 | django-markdownfield comes with a number of validators, which are used to process and clean 73 | the output of the markdown engine 74 | 75 | ### VALIDATOR_STANDARD 76 | ```python 77 | from markdownfield.validators import VALIDATOR_STANDARD 78 | ``` 79 | This validator strips any tags that are not used by standard Markdown. It also automatically links 80 | any URLs in the output, adding `class="external"`, `rel="nofollow noopener noreferrer"`, and 81 | `target="_blank"` to any URLs which it determines to be external. 82 | 83 | ### VALIDATOR_CLASSY 84 | ```python 85 | from markdownfield.validators import VALIDATOR_CLASSY 86 | ``` 87 | This validator does much the same as `VALIDATOR_STANDARD`, but it allows you to set the class on 88 | links and images. This is useful to create buttons and other enhanced links. 89 | 90 | ### VALIDATOR_NULL 91 | ```python 92 | from markdownfield.validators import VALIDATOR_NULL 93 | ``` 94 | This validator does not call [bleach](https://github.com/mozilla/bleach) to sanitize the output at all. 95 | This is **not safe for user input**. It allows arbitrary (unsafe) HTML in your markdown input. 96 | 97 | 98 | ### Creating Custom Validators 99 | To create a custom validator, just create an instance of the `markdownfield.validators.Validator` 100 | dataclass. An example of this is shown below. 101 | 102 | ```python 103 | from markdownfield.validators import Validator 104 | 105 | # allows only bold and italic text 106 | VALIDATOR_COMMENTS = Validator( 107 | allowed_tags=["b", "i", "strong", "em"], 108 | allowed_attrs={}, 109 | linkify=False 110 | ) 111 | ``` 112 | 113 | You can also find a standard set of markdown-safe tags and attrs in `markdownfield.validators`, and extend 114 | that. 115 | 116 | ```python 117 | from markdownfield.validators import Validator, MARKDOWN_TAGS, MARKDOWN_ATTRS 118 | 119 | # allows all standard markdown features, 120 | # but also allows the class to be set on images and links 121 | VALIDATOR_CLASSY = Validator( 122 | allowed_tags=MARKDOWN_TAGS, 123 | allowed_attrs={ 124 | **MARKDOWN_ATTRS, 125 | 'img': ['src', 'alt', 'title', 'class'], 126 | 'a': ['href', 'alt', 'title', 'name', 'class'] 127 | } 128 | ) 129 | ``` 130 | 131 | ## Migrations 132 | 133 | If you need to migrate from TextField or CharField to the MarkdownField you need to migrate the stored `text` also in the `rendered_text` field. 134 | Update your auto-created migration file and add the method below. 135 | Use a method to `save()` every instance of your model once after the migrations, so the text will be copied into the `text_rendered` field correctly. 136 | 137 | ```python 138 | from django.db import migrations 139 | import markdownfield.models 140 | 141 | 142 | def save_text_rendered(apps, schema_editor): 143 | ExampleModel = apps.get_model('yourapp', 'ExampleModel') 144 | for examplemodel in ExampleModel.objects.all(): 145 | examplemodel.save() 146 | 147 | 148 | class Migration(migrations.Migration): 149 | 150 | dependencies = [ 151 | ('yourapp', '000X_migrate_to_markdownfield'), 152 | ] 153 | 154 | operations = [ 155 | migrations.AddField( 156 | model_name='yourapp', 157 | name='text_rendered', 158 | field=markdownfield.models.RenderedMarkdownField(default=''), 159 | preserve_default=False, 160 | ), 161 | migrations.AlterField( 162 | model_name='ExampleModel', 163 | name='text', 164 | field=markdownfield.models.MarkdownField(rendered_field='text_rendered'), 165 | ), 166 | migrations.RunPython(save_text_rendered), 167 | ] 168 | ``` 169 | 170 | ## License 171 | 172 | This software is released under the MIT license. 173 | ``` 174 | Copyright (c) 2019-2021 Luke Rogers 175 | 176 | Permission is hereby granted, free of charge, to any person obtaining a copy 177 | of this software and associated documentation files (the "Software"), to deal 178 | in the Software without restriction, including without limitation the rights 179 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 180 | copies of the Software, and to permit persons to whom the Software is 181 | furnished to do so, subject to the following conditions: 182 | 183 | The above copyright notice and this permission notice shall be included in all 184 | copies or substantial portions of the Software. 185 | 186 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 187 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 188 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 189 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 190 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 191 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 192 | SOFTWARE. 193 | ``` 194 | -------------------------------------------------------------------------------- /markdownfield/__init__.py: -------------------------------------------------------------------------------- 1 | """A simple custom field for Django that can safely render Markdown and store it in the database. 2 | """ 3 | __version__ = '0.11.0' 4 | -------------------------------------------------------------------------------- /markdownfield/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MarkdownFieldConfig(AppConfig): 5 | name = 'markdownfield' 6 | verbose_name = 'Markdown Field' 7 | -------------------------------------------------------------------------------- /markdownfield/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import CharField 2 | 3 | from .widgets import MDEWidget 4 | 5 | 6 | class MarkdownFormField(CharField): 7 | widget = MDEWidget 8 | -------------------------------------------------------------------------------- /markdownfield/models.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from django.conf import settings 4 | from django.contrib.admin import widgets as admin_widgets 5 | from django.db.models import TextField 6 | 7 | import bleach 8 | from bleach.linkifier import LinkifyFilter 9 | from markdown import markdown 10 | 11 | from .forms import MarkdownFormField 12 | from .util import blacklist_link, format_link 13 | from .validators import VALIDATOR_STANDARD, Validator 14 | from .widgets import MDEAdminWidget 15 | 16 | EXTENSIONS = getattr(settings, 'MARKDOWN_EXTENSIONS', ['fenced_code']) 17 | EXTENSION_CONFIGS = getattr(settings, 'MARKDOWN_EXTENSION_CONFIGS', {}) 18 | 19 | 20 | class RenderedMarkdownField(TextField): 21 | """ 22 | RenderedMarkdownField is pretty much just a plain textfield that doesn't show up in the admin panel. 23 | 24 | Using a custom field type also allows more functionality (eg; custom display rules, automatic mark_safe) 25 | to be added in the future. 26 | """ 27 | 28 | def __init__(self, *args, **kwargs): 29 | kwargs['editable'] = False 30 | kwargs['blank'] = False 31 | super().__init__(*args, **kwargs) 32 | 33 | def deconstruct(self): 34 | name, path, args, kwargs = super().deconstruct() 35 | del kwargs['editable'] 36 | return name, path, args, kwargs 37 | 38 | 39 | class MarkdownField(TextField): 40 | def __init__(self, *args, 41 | rendered_field: str = None, 42 | validator: Validator = VALIDATOR_STANDARD, 43 | use_editor: bool = True, 44 | use_admin_editor: bool = True, 45 | **kwargs): 46 | self.rendered_field = rendered_field 47 | self.use_editor = use_editor 48 | self.use_admin_editor = use_admin_editor 49 | self.validator = validator 50 | super().__init__(*args, **kwargs) 51 | 52 | def deconstruct(self): 53 | # todo: deconstruct validators. maybe. 54 | name, path, args, kwargs = super().deconstruct() 55 | if self.rendered_field is not None: 56 | kwargs['rendered_field'] = self.rendered_field 57 | if self.use_editor is not True: 58 | kwargs['use_editor'] = self.use_editor 59 | if self.use_admin_editor is not True: 60 | kwargs['use_admin_editor'] = self.use_admin_editor 61 | return name, path, args, kwargs 62 | 63 | def formfield(self, **kwargs): 64 | defaults = {} 65 | if self.use_editor: 66 | defaults = {'form_class': MarkdownFormField} 67 | 68 | defaults.update(kwargs) 69 | 70 | if self.use_admin_editor: 71 | if 'widget' in defaults and defaults['widget'] == admin_widgets.AdminTextareaWidget: 72 | defaults['widget'] = MDEAdminWidget() 73 | 74 | return super().formfield(**defaults) 75 | 76 | def pre_save(self, model_instance, add): 77 | value = super().pre_save(model_instance, add) 78 | 79 | if not self.rendered_field: 80 | return value 81 | 82 | dirty = markdown( 83 | text=value, 84 | extensions=EXTENSIONS, 85 | extension_configs=EXTENSION_CONFIGS 86 | ) 87 | 88 | if self.validator.sanitize: 89 | if self.validator.linkify: 90 | cleaner = bleach.Cleaner(tags=self.validator.allowed_tags, 91 | attributes=self.validator.allowed_attrs, 92 | css_sanitizer=self.validator.css_sanitizer, 93 | filters=[partial(LinkifyFilter, 94 | callbacks=[format_link, blacklist_link])]) 95 | else: 96 | cleaner = bleach.Cleaner(tags=self.validator.allowed_tags, 97 | attributes=self.validator.allowed_attrs, 98 | css_sanitizer=self.validator.css_sanitizer) 99 | 100 | clean = cleaner.clean(dirty) 101 | setattr(model_instance, self.rendered_field, clean) 102 | else: 103 | # danger! 104 | setattr(model_instance, self.rendered_field, dirty) 105 | 106 | return value 107 | -------------------------------------------------------------------------------- /markdownfield/static/markdownfield/easymde/easymde.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * easymde v2.14.0 3 | * Copyright Jeroen Akkerman 4 | * @link https://github.com/ionaru/easy-markdown-editor 5 | * @license MIT 6 | */ 7 | .CodeMirror{font-family:monospace;height:300px;color:#000;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:transparent}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.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,.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{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.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}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.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-2{color:#05a}.cm-s-default .cm-type,.cm-s-default .cm-variable-3{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:red}.cm-invalidchar{color:red}.CodeMirror-composing{border-bottom:2px solid}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}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-50px;margin-right:-50px;padding-bottom:50px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:50px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none;outline:0}.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:-50px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:0 0!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}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;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:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-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)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0 0}.EasyMDEContainer{display:block}.EasyMDEContainer.sided--no-fullscreen{display:flex;flex-direction:row;flex-wrap:wrap}.EasyMDEContainer .CodeMirror{box-sizing:border-box;height:auto;border:1px solid #ddd;border-bottom-left-radius:4px;border-bottom-right-radius:4px;padding:10px;font:inherit;z-index:0;word-wrap:break-word}.EasyMDEContainer .CodeMirror-scroll{cursor:text}.EasyMDEContainer .CodeMirror-fullscreen{background:#fff;position:fixed!important;top:50px;left:0;right:0;bottom:0;height:auto;z-index:8;border-right:none!important;border-bottom-right-radius:0!important}.EasyMDEContainer .CodeMirror-sided{width:50%!important}.EasyMDEContainer .CodeMirror-sided.sided--no-fullscreen{border-right:none!important;border-bottom-right-radius:0;position:relative;flex:1 1 auto}.EasyMDEContainer .CodeMirror-placeholder{opacity:.5}.EasyMDEContainer .CodeMirror-focused .CodeMirror-selected{background:#d9d9d9}.editor-toolbar{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;padding:9px 10px;border-top:1px solid #bbb;border-left:1px solid #bbb;border-right:1px solid #bbb;border-top-left-radius:4px;border-top-right-radius:4px}.editor-toolbar.fullscreen{width:100%;height:50px;padding-top:10px;padding-bottom:10px;box-sizing:border-box;background:#fff;border:0;position:fixed;top:0;left:0;opacity:1;z-index:9}.editor-toolbar.fullscreen::before{width:20px;height:50px;background:-moz-linear-gradient(left,#fff 0,rgba(255,255,255,0) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,#fff),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,#fff 0,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,#fff 0,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,#fff 0,rgba(255,255,255,0) 100%);background:linear-gradient(to right,#fff 0,rgba(255,255,255,0) 100%);position:fixed;top:0;left:0;margin:0;padding:0}.editor-toolbar.fullscreen::after{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,#fff 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,0)),color-stop(100%,#fff));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,#fff 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,#fff 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,#fff 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,#fff 100%);position:fixed;top:0;right:0;margin:0;padding:0}.editor-toolbar.sided--no-fullscreen{width:100%}.editor-toolbar .easymde-dropdown,.editor-toolbar button{background:0 0;display:inline-block;text-align:center;text-decoration:none!important;height:30px;margin:0;padding:0;border:1px solid transparent;border-radius:3px;cursor:pointer}.editor-toolbar button{width:30px}.editor-toolbar button.active,.editor-toolbar button:hover{background:#fcfcfc;border-color:#95a5a6}.editor-toolbar i.separator{display:inline-block;width:0;border-left:1px solid #d9d9d9;border-right:1px solid #fff;color:transparent;text-indent:-10px;margin:0 6px}.editor-toolbar button:after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;font-size:65%;vertical-align:text-bottom;position:relative;top:2px}.editor-toolbar button.heading-1:after{content:"1"}.editor-toolbar button.heading-2:after{content:"2"}.editor-toolbar button.heading-3:after{content:"3"}.editor-toolbar button.heading-bigger:after{content:"▲"}.editor-toolbar button.heading-smaller:after{content:"▼"}.editor-toolbar.disabled-for-preview button:not(.no-disable){opacity:.6;pointer-events:none}@media only screen and (max-width:700px){.editor-toolbar i.no-mobile{display:none}}.editor-statusbar{padding:8px 10px;font-size:12px;color:#959694;text-align:right}.editor-statusbar.sided--no-fullscreen{width:100%}.editor-statusbar span{display:inline-block;min-width:4em;margin-left:1em}.editor-statusbar .lines:before{content:'lines: '}.editor-statusbar .words:before{content:'words: '}.editor-statusbar .characters:before{content:'characters: '}.editor-preview-full{position:absolute;width:100%;height:100%;top:0;left:0;z-index:7;overflow:auto;display:none;box-sizing:border-box}.editor-preview-side{position:fixed;bottom:0;width:50%;top:50px;right:0;z-index:9;overflow:auto;display:none;box-sizing:border-box;border:1px solid #ddd;word-wrap:break-word}.editor-preview-active-side{display:block}.editor-preview-active-side.sided--no-fullscreen{flex:1 1 auto;height:auto;position:static}.editor-preview-active{display:block}.editor-preview{padding:10px;background:#fafafa}.editor-preview>p{margin-top:0}.editor-preview pre{background:#eee;margin-bottom:10px}.editor-preview table td,.editor-preview table th{border:1px solid #ddd;padding:5px}.cm-s-easymde .cm-tag{color:#63a35c}.cm-s-easymde .cm-attribute{color:#795da3}.cm-s-easymde .cm-string{color:#183691}.cm-s-easymde .cm-header-1{font-size:200%;line-height:200%}.cm-s-easymde .cm-header-2{font-size:160%;line-height:160%}.cm-s-easymde .cm-header-3{font-size:125%;line-height:125%}.cm-s-easymde .cm-header-4{font-size:110%;line-height:110%}.cm-s-easymde .cm-comment{background:rgba(0,0,0,.05);border-radius:2px}.cm-s-easymde .cm-link{color:#7f8c8d}.cm-s-easymde .cm-url{color:#aab2b3}.cm-s-easymde .cm-quote{color:#7f8c8d;font-style:italic}.editor-toolbar .easymde-dropdown{position:relative;background:linear-gradient(to bottom right,#fff 0,#fff 84%,#333 50%,#333 100%);border-radius:0;border:1px solid #fff}.editor-toolbar .easymde-dropdown:hover{background:linear-gradient(to bottom right,#fff 0,#fff 84%,#333 50%,#333 100%)}.easymde-dropdown-content{display:block;visibility:hidden;position:absolute;background-color:#f9f9f9;box-shadow:0 8px 16px 0 rgba(0,0,0,.2);padding:8px;z-index:2;top:30px}.easymde-dropdown:active .easymde-dropdown-content,.easymde-dropdown:focus .easymde-dropdown-content{visibility:visible}span[data-img-src]::after{content:'';background-image:var(--bg-image);display:block;max-height:100%;max-width:100%;background-size:contain;height:0;padding-top:var(--height);width:var(--width);background-repeat:no-repeat}.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){background:rgba(255,0,0,.15)} 8 | -------------------------------------------------------------------------------- /markdownfield/static/markdownfield/fontawesome/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('./fonts/fontawesome-webfont.eot?v=4.7.0');src:url('./fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('./fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('./fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('./fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('./fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /markdownfield/static/markdownfield/fontawesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmptrluke/django-markdownfield/fc64e4d95dda56a24c1358e208f5ea1d75adac96/markdownfield/static/markdownfield/fontawesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /markdownfield/static/markdownfield/fontawesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmptrluke/django-markdownfield/fc64e4d95dda56a24c1358e208f5ea1d75adac96/markdownfield/static/markdownfield/fontawesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /markdownfield/static/markdownfield/fontawesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmptrluke/django-markdownfield/fc64e4d95dda56a24c1358e208f5ea1d75adac96/markdownfield/static/markdownfield/fontawesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /markdownfield/static/markdownfield/fontawesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmptrluke/django-markdownfield/fc64e4d95dda56a24c1358e208f5ea1d75adac96/markdownfield/static/markdownfield/fontawesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /markdownfield/static/markdownfield/fontawesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmptrluke/django-markdownfield/fc64e4d95dda56a24c1358e208f5ea1d75adac96/markdownfield/static/markdownfield/fontawesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /markdownfield/static/markdownfield/md.css: -------------------------------------------------------------------------------- 1 | .cm-s-easymde .cm-formatting-emoji { 2 | color: #183691; 3 | } 4 | -------------------------------------------------------------------------------- /markdownfield/static/markdownfield/md_admin.css: -------------------------------------------------------------------------------- 1 | .cm-s-easymde.CodeMirror-fullscreen, .editor-toolbar.fullscreen { 2 | margin: 0 !important; 3 | z-index: 20; 4 | } 5 | 6 | .EasyMDEContainer .editor-toolbar { 7 | background-color: var(--primary); 8 | border: none; 9 | } 10 | 11 | .EasyMDEContainer .editor-toolbar button { 12 | color: var(--button-fg); 13 | background-color: var(--button-bg); 14 | } 15 | 16 | .EasyMDEContainer .editor-toolbar button:hover, 17 | .EasyMDEContainer .editor-toolbar button.active { 18 | border: none; 19 | background-color: var(--button-hover-bg); 20 | } 21 | 22 | .EasyMDEContainer .CodeMirror { 23 | color: var(--body-fg); 24 | background-color: var(--body-bg); 25 | border: 1px solid var(--border-color) !important; 26 | } 27 | 28 | .EasyMDEContainer .CodeMirror-cursor { 29 | border-left: 1px solid var(--body-fg) !important; 30 | } 31 | 32 | .editor-preview { 33 | -webkit-text-size-adjust: 100%; 34 | -ms-text-size-adjust: 100%; 35 | font-size: 100%; 36 | font-size: 1em; 37 | line-height: 1.5em 38 | } 39 | 40 | .editor-preview * { 41 | line-height: 1.5 42 | } 43 | 44 | .editor-preview a { 45 | color: #5b80b2; 46 | text-decoration: underline 47 | } 48 | 49 | .editor-preview a:visited { 50 | color: #0b0080 51 | } 52 | 53 | .editor-preview a:hover { 54 | color: #0645ad 55 | } 56 | 57 | .editor-preview a:active { 58 | color: #faa700 59 | } 60 | 61 | .editor-preview a:focus { 62 | outline: thin dotted 63 | } 64 | 65 | .editor-preview a:active, 66 | .editor-preview a:hover { 67 | outline: 0 68 | } 69 | 70 | .editor-preview p { 71 | margin: 1em 0; 72 | padding: 0; 73 | font-size: 14px 74 | } 75 | 76 | .editor-preview img { 77 | max-width: 100% 78 | } 79 | 80 | .editor-preview h1, 81 | .editor-preview h2, 82 | .editor-preview h3, 83 | .editor-preview h4, 84 | .editor-preview h5, 85 | .editor-preview h6 { 86 | font-weight: 400; 87 | color: #111; 88 | margin-top: .75em; 89 | margin-bottom: .75em; 90 | padding: 0; 91 | background: 0 0 92 | } 93 | 94 | .editor-preview h4, 95 | .editor-preview h5, 96 | .editor-preview h6 { 97 | font-weight: 700 98 | } 99 | 100 | .editor-preview h1 { 101 | font-size: 2.5em 102 | } 103 | 104 | .editor-preview h2 { 105 | font-size: 2em 106 | } 107 | 108 | .editor-preview h3 { 109 | font-size: 1.5em 110 | } 111 | 112 | .editor-preview h4 { 113 | font-size: 1.2em 114 | } 115 | 116 | .editor-preview h5 { 117 | font-size: 1em 118 | } 119 | 120 | .editor-preview h6 { 121 | font-size: .9em 122 | } 123 | 124 | .editor-preview blockquote { 125 | color: #666; 126 | margin: 0; 127 | padding-left: 1.5em; 128 | border-left: .5em #eee solid 129 | } 130 | 131 | .editor-preview hr { 132 | display: block; 133 | height: 0; 134 | border: 0; 135 | font-style: italic; 136 | border-bottom: 1px solid #ccc; 137 | margin: 20px 0; 138 | padding: 0 139 | } 140 | 141 | .editor-preview code, 142 | .editor-preview kbd, 143 | .editor-preview pre, 144 | .editor-preview samp { 145 | font-family: monospace, monospace; 146 | font-size: 14px 147 | } 148 | 149 | .editor-preview code, 150 | .editor-preview pre { 151 | margin: 0 2px; 152 | padding: 0 5px; 153 | border: 1px solid #ddd; 154 | background-color: #f8f8f8; 155 | border-radius: 2px; 156 | color: #444 157 | } 158 | 159 | .editor-preview pre { 160 | margin: 1.5em 0 1.5em 0; 161 | padding: 1em; 162 | white-space: pre; 163 | white-space: pre-wrap; 164 | word-wrap: break-word 165 | } 166 | 167 | .editor-preview pre code { 168 | margin: 0; 169 | padding: 0; 170 | background: 0 0; 171 | border: none 172 | } 173 | 174 | .editor-preview b, 175 | .editor-preview strong { 176 | font-weight: 700 177 | } 178 | 179 | .editor-preview dfn { 180 | font-style: italic 181 | } 182 | 183 | .editor-preview ins { 184 | background: #ff9; 185 | color: #000; 186 | text-decoration: none 187 | } 188 | 189 | .editor-preview mark { 190 | background: #ff0; 191 | color: #000; 192 | font-style: italic; 193 | font-weight: 700 194 | } 195 | 196 | .editor-preview sub, 197 | .editor-preview sup { 198 | font-size: 75%; 199 | line-height: 0; 200 | position: relative; 201 | vertical-align: baseline 202 | } 203 | 204 | .editor-preview sup { 205 | top: -.5em 206 | } 207 | 208 | .editor-preview sub { 209 | bottom: -.25em 210 | } 211 | 212 | .editor-preview ol, 213 | .editor-preview ul { 214 | list-style: unset; 215 | margin: 1em 0 !important; 216 | padding: 0 0 0 2em !important 217 | } 218 | 219 | .editor-preview ol li, 220 | .editor-preview ul li { 221 | font-size: 14px !important; 222 | margin-bottom: .1em 223 | } 224 | 225 | .editor-preview li p:last-child { 226 | margin: 0 227 | } 228 | 229 | .editor-preview dd { 230 | margin: 0 0 0 2em 231 | } 232 | 233 | .editor-preview img { 234 | border: 0; 235 | -ms-interpolation-mode: bicubic; 236 | vertical-align: middle 237 | } 238 | 239 | .editor-preview table { 240 | border-collapse: collapse; 241 | border-spacing: 0 242 | } 243 | 244 | .editor-preview th { 245 | background: 0 0; 246 | background: #f8f8f8; 247 | font-size: 14px 248 | } 249 | 250 | .editor-preview td { 251 | vertical-align: top; 252 | font-size: 14px 253 | } 254 | -------------------------------------------------------------------------------- /markdownfield/templates/markdownfield/widget.html: -------------------------------------------------------------------------------- 1 | {% include 'django/forms/widgets/textarea.html' %} 2 | {{ options | json_script:options_id }} 3 | 14 | -------------------------------------------------------------------------------- /markdownfield/util.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | from urllib.parse import urlparse 3 | 4 | from django.conf import settings 5 | 6 | BLACKLIST = getattr(settings, 'MARKDOWN_LINKIFY_BLACKLIST', []) 7 | 8 | 9 | def blacklist_link(attrs: Dict[tuple, str], new: bool = False): 10 | try: 11 | p = urlparse(attrs[(None, 'href')]) 12 | except KeyError: 13 | return attrs 14 | 15 | if (p.netloc in BLACKLIST) and new: 16 | return None 17 | return attrs 18 | 19 | 20 | def format_link(attrs: Dict[tuple, str], new: bool = False): 21 | """ 22 | This is really weird and ugly, but that's how bleach linkify filters work. 23 | """ 24 | try: 25 | p = urlparse(attrs[(None, 'href')]) 26 | except KeyError: 27 | # no href, probably an anchor 28 | return attrs 29 | 30 | if not any([p.scheme, p.netloc, p.path]) and p.fragment: 31 | # the link isn't going anywhere, probably a fragment link 32 | return attrs 33 | 34 | if hasattr(settings, 'SITE_URL'): 35 | c = urlparse(settings.SITE_URL) 36 | link_is_external = p.netloc != c.netloc 37 | else: 38 | # Assume true for safety 39 | link_is_external = True 40 | 41 | if link_is_external: 42 | # link is external - secure and mark 43 | attrs[(None, 'target')] = '_blank' 44 | attrs[(None, 'class')] = attrs.get((None, 'class'), '') + ' external' 45 | attrs[(None, 'rel')] = 'nofollow noopener noreferrer' 46 | 47 | return attrs 48 | -------------------------------------------------------------------------------- /markdownfield/validators.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Dict, List, Optional 3 | 4 | from bleach.css_sanitizer import CSSSanitizer 5 | 6 | MARKDOWN_TAGS = [ 7 | "h1", "h2", "h3", "h4", "h5", "h6", 8 | "b", "i", "strong", "em", "tt", "del", "abbr", 9 | "p", "br", 10 | "span", "div", "blockquote", "code", "pre", "hr", 11 | "ul", "dl", "ol", "li", "dd", "dt", 12 | "img", 13 | "a", 14 | "sub", "sup", 15 | ] 16 | 17 | MARKDOWN_ATTRS = { 18 | "*": ["id"], 19 | "img": ["src", "alt", "title"], 20 | "a": ["href", "alt", "title"], 21 | "abbr": ["title"], 22 | } 23 | 24 | 25 | @dataclass 26 | class Validator: 27 | """ defines a standard format for markdown validators """ 28 | allowed_tags: List[str] 29 | allowed_attrs: Dict[str, List[str]] 30 | css_sanitizer: Optional[CSSSanitizer] = None 31 | sanitize: bool = True 32 | linkify: bool = True 33 | 34 | 35 | VALIDATOR_NULL = Validator( 36 | allowed_tags=[], 37 | allowed_attrs={}, 38 | sanitize=False, 39 | linkify=False 40 | ) 41 | 42 | VALIDATOR_STANDARD = Validator( 43 | allowed_tags=MARKDOWN_TAGS, 44 | allowed_attrs=MARKDOWN_ATTRS, 45 | ) 46 | 47 | VALIDATOR_CLASSY = Validator( 48 | allowed_tags=MARKDOWN_TAGS, 49 | allowed_attrs={ 50 | **MARKDOWN_ATTRS, 51 | 'img': ['src', 'alt', 'title', 'class'], 52 | 'a': ['href', 'alt', 'title', 'name', 'class'] 53 | }, 54 | ) 55 | -------------------------------------------------------------------------------- /markdownfield/widgets.py: -------------------------------------------------------------------------------- 1 | from django.forms import widgets 2 | 3 | import shortuuid 4 | 5 | 6 | class MDEWidget(widgets.Textarea): 7 | template_name = 'markdownfield/widget.html' 8 | 9 | def __init__(self, options=None, **kwargs): 10 | super().__init__(**kwargs) 11 | self.uuid = shortuuid.uuid() 12 | 13 | if options is None: 14 | options = {} 15 | 16 | self.options = options 17 | self.options_id = 'options_' + self.uuid 18 | 19 | def get_context(self, *args): 20 | context = super().get_context(*args) 21 | context.update({ 22 | 'options': self.options, 23 | 'options_id': self.options_id, 24 | }) 25 | return context 26 | 27 | class Media: 28 | js = ( 29 | 'markdownfield/easymde/easymde.min.js', 30 | ) 31 | 32 | css = { 33 | 'all': ( 34 | 'markdownfield/easymde/easymde.min.css', 35 | 'markdownfield/fontawesome/font-awesome.min.css', 36 | 'markdownfield/md.css', 37 | ) 38 | } 39 | 40 | 41 | class MDEAdminWidget(MDEWidget): 42 | template_name = 'markdownfield/widget.html' 43 | 44 | class Media: 45 | css = { 46 | 'all': ( 47 | 'markdownfield/md_admin.css', 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.4,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [tool.flit.metadata] 6 | dist-name = "django-markdownfield" 7 | module = "markdownfield" 8 | description-file = "README.md" 9 | classifiers = [ 10 | "Programming Language :: Python :: 3", 11 | "Programming Language :: Python :: 3.8", 12 | "Programming Language :: Python :: 3.9", 13 | "Programming Language :: Python :: 3.10", 14 | "Programming Language :: Python :: 3.11", 15 | "Development Status :: 4 - Beta", 16 | "Framework :: Django", 17 | "Intended Audience :: Developers", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: OS Independent", 20 | ] 21 | author = "Luke Rogers" 22 | author-email = "luke@dmptr.com" 23 | home-page = "https://github.com/dmptrluke/django-markdownfield" 24 | requires-python=">=3.8" 25 | requires = [ 26 | 'django>=3.2', 27 | 'bleach[css]>=5.0.1', 28 | 'markdown', 29 | 'shortuuid', 30 | 'dataclasses; python_version == "3.6"', 31 | ] 32 | 33 | [tool.isort] 34 | line_length = 100 35 | skip_glob = "*/node_modules/*,*/.tox/*,*/.git/*" 36 | balanced_wrapping = true 37 | known_third_party = ["bleach", "markdown", "shortuuid"] 38 | known_django = ["django"] 39 | sections = ["FUTURE","STDLIB","DJANGO","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"] 40 | -------------------------------------------------------------------------------- /screenshots/editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmptrluke/django-markdownfield/fc64e4d95dda56a24c1358e208f5ea1d75adac96/screenshots/editor.png --------------------------------------------------------------------------------