[
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.vscode\n.idea\n\nvenv/\n.venv/\ndist/\n__pycache__\n\n#egg's specific\n*.egg-info\n\ndb.sqlite3\n"
  },
  {
    "path": ".pylintrc",
    "content": "[MASTER]\ndisable=\n    C0114, # missing-module-docstring\n    C0115,\n    C0116,\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Ilya Kotlyakov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Editor.js for Django\n\nDjango plugin for using [Editor.js](https://editorjs.io/)\n\n> This plugin works fine with JSONField in Django >= 3.1\n\n[![Django Editor.js](https://i.ibb.co/r6xt4HJ/image.png)](https://github.com/2ik/django-editorjs-fields)\n\n[![Python versions](https://img.shields.io/pypi/pyversions/django-editorjs-fields)](https://pypi.org/project/django-editorjs-fields/)\n[![Python versions](https://img.shields.io/pypi/djversions/django-editorjs-fields)](https://pypi.org/project/django-editorjs-fields/)\n[![Downloads](https://static.pepy.tech/personalized-badge/django-editorjs-fields?period=total&units=international_system&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/django-editorjs-fields)\n\n## Installation\n\n```bash\npip install django-editorjs-fields\n```\n\nAdd `django_editorjs_fields` to `INSTALLED_APPS` in `settings.py` for your project:\n\n```python\n# settings.py\nINSTALLED_APPS = [\n    ...\n    'django_editorjs_fields',\n]\n```\n\n## Upgrade\n\n```bash\npip install django-editorjs-fields --upgrade\npython manage.py collectstatic  # upgrade js and css files\n```\n\n## Usage\n\nAdd code in your model\n\n```python\n# models.py\nfrom django.db import models\nfrom django_editorjs_fields import EditorJsJSONField  # Django >= 3.1\nfrom django_editorjs_fields import EditorJsTextField\n\n\nclass Post(models.Model):\n    body_default = models.TextField()\n    body_editorjs = EditorJsJSONField()  # Django >= 3.1\n    body_editorjs_text = EditorJsTextField()\n\n```\n\n#### New in version 0.2.1. Django Templates support\n\n```html\n<!-- template.html -->\n\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    {% load editorjs %}\n    {{ post.body_default }}\n    {{ post.body_editorjs | editorjs}}\n    {{ post.body_editorjs_text | editorjs}}\n  </body>\n</html>\n```\n\n## Additionally\n\nYou can add custom Editor.js plugins and configs ([List plugins](https://github.com/editor-js/awesome-editorjs))\n\nExample custom field in models.py\n\n```python\n# models.py\nfrom django.db import models\nfrom django_editorjs_fields import EditorJsJSONField\n\n\nclass Post(models.Model):\n    body_editorjs_custom = EditorJsJSONField(\n        plugins=[\n            \"@editorjs/image\",\n            \"@editorjs/header\",\n            \"editorjs-github-gist-plugin\",\n            \"@editorjs/code@2.6.0\",  # version allowed :)\n            \"@editorjs/list@latest\",\n            \"@editorjs/inline-code\",\n            \"@editorjs/table\",\n        ],\n        tools={\n            \"Gist\": {\n                \"class\": \"Gist\"  # Include the plugin class. See docs Editor.js plugins\n            },\n            \"Image\": {\n                \"config\": {\n                    \"endpoints\": {\n                        \"byFile\": \"/editorjs/image_upload/\"  # Your custom backend file uploader endpoint\n                    }\n                }\n            }\n        },\n        i18n={\n            'messages': {\n                'blockTunes': {\n                    \"delete\": {\n                        \"Delete\": \"Удалить\"\n                    },\n                    \"moveUp\": {\n                        \"Move up\": \"Переместить вверх\"\n                    },\n                    \"moveDown\": {\n                        \"Move down\": \"Переместить вниз\"\n                    }\n                }\n            },\n        }\n        null=True,\n        blank=True\n    )\n\n```\n\n**django-editorjs-fields** support this list of Editor.js plugins by default:\n<a name=\"plugins\"></a>\n\n<details>\n    <summary>EDITORJS_DEFAULT_PLUGINS</summary>\n\n```python\nEDITORJS_DEFAULT_PLUGINS = (\n    '@editorjs/paragraph',\n    '@editorjs/image',\n    '@editorjs/header',\n    '@editorjs/list',\n    '@editorjs/checklist',\n    '@editorjs/quote',\n    '@editorjs/raw',\n    '@editorjs/code',\n    '@editorjs/inline-code',\n    '@editorjs/embed',\n    '@editorjs/delimiter',\n    '@editorjs/warning',\n    '@editorjs/link',\n    '@editorjs/marker',\n    '@editorjs/table',\n)\n```\n\n</details>\n\n<details>\n    <summary>EDITORJS_DEFAULT_CONFIG_TOOLS</summary>\n\n```python\nEDITORJS_DEFAULT_CONFIG_TOOLS = {\n    'Image': {\n        'class': 'ImageTool',\n        'inlineToolbar': True,\n        \"config\": {\n            \"endpoints\": {\n                \"byFile\": reverse_lazy('editorjs_image_upload'),\n                \"byUrl\": reverse_lazy('editorjs_image_by_url')\n            }\n        },\n    },\n    'Header': {\n        'class': 'Header',\n        'inlineToolbar': True,\n        'config': {\n            'placeholder': 'Enter a header',\n            'levels': [2, 3, 4],\n            'defaultLevel': 2,\n        }\n    },\n    'Checklist': {'class': 'Checklist', 'inlineToolbar': True},\n    'List': {'class': 'List', 'inlineToolbar': True},\n    'Quote': {'class': 'Quote', 'inlineToolbar': True},\n    'Raw': {'class': 'RawTool'},\n    'Code': {'class': 'CodeTool'},\n    'InlineCode': {'class': 'InlineCode'},\n    'Embed': {'class': 'Embed'},\n    'Delimiter': {'class': 'Delimiter'},\n    'Warning': {'class': 'Warning', 'inlineToolbar': True},\n    'LinkTool': {\n        'class': 'LinkTool',\n        'config': {\n            'endpoint': reverse_lazy('editorjs_linktool'),\n        }\n    },\n    'Marker': {'class': 'Marker', 'inlineToolbar': True},\n    'Table': {'class': 'Table', 'inlineToolbar': True},\n}\n```\n\n</details>\n\n`EditorJsJSONField` accepts all the arguments of `JSONField` class.\n\n`EditorJsTextField` accepts all the arguments of `TextField` class.\n\nAdditionally, it includes arguments such as:\n\n| Args            | Description                                                                                                                                  | Default                         |\n| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |\n| `plugins`       | List plugins Editor.js                                                                                                                       | `EDITORJS_DEFAULT_PLUGINS`      |\n| `tools`         | Map of Tools to use. Set config `tools` for Editor.js [See docs](https://editorjs.io/configuration#passing-saved-data)                       | `EDITORJS_DEFAULT_CONFIG_TOOLS` |\n| `use_editor_js` | Enables or disables the Editor.js plugin for the field                                                                                       | `True`                          |\n| `autofocus`     | If true, set caret at the first Block after Editor is ready                                                                                  | `False`                         |\n| `hideToolbar`   | If true, toolbar won't be shown                                                                                                              | `False`                         |\n| `inlineToolbar` | Defines default toolbar for all tools.                                                                                                       | `True`                          |\n| `readOnly`      | Enable read-only mode                                                                                                                        | `False`                         |\n| `minHeight`     | Height of Editor's bottom area that allows to set focus on the last Block                                                                    | `300`                           |\n| `logLevel`      | Editors log level (how many logs you want to see)                                                                                            | `ERROR`                         |\n| `placeholder`   | First Block placeholder                                                                                                                      | `Type text...`                  |\n| `defaultBlock`  | This Tool will be used as default. Name should be equal to one of Tool`s keys of passed tools. If not specified, Paragraph Tool will be used | `paragraph`                     |\n| `i18n`          | Internalization config                                                                                                                       | `{}`                            |\n| `sanitizer`     | Define default sanitizer configuration                                                                                                       | `{ p: true, b: true, a: true }` |\n\n## Image uploads\n\nIf you want to upload images to the editor then add `django_editorjs_fields.urls` to `urls.py` for your project with `DEBUG=True`:\n\n```python\n# urls.py\nfrom django.contrib import admin\nfrom django.urls import path, include\nfrom django.conf import settings\nfrom django.conf.urls.static import static\n\nurlpatterns = [\n    ...\n    path('editorjs/', include('django_editorjs_fields.urls')),\n    ...\n] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)\n```\n\nIn production `DEBUG=False` (use nginx to display images):\n\n```python\n# urls.py\nfrom django.contrib import admin\nfrom django.urls import path, include\n\nurlpatterns = [\n    ...\n    path('editorjs/', include('django_editorjs_fields.urls')),\n    ...\n]\n```\n\nSee an example of how you can work with the plugin [here](https://github.com/2ik/django-editorjs-fields/blob/main/example)\n\n## Forms\n\n```python\nfrom django import forms\nfrom django_editorjs_fields import EditorJsWidget\n\n\nclass TestForm(forms.ModelForm):\n    class Meta:\n        model = Post\n        exclude = []\n        widgets = {\n            'body_editorjs': EditorJsWidget(config={'minHeight': 100}),\n            'body_editorjs_text': EditorJsWidget(plugins=[\"@editorjs/image\", \"@editorjs/header\"])\n        }\n```\n\n## Theme\n\n### Default Theme\n\n![image](https://user-images.githubusercontent.com/6692517/124242133-7a7dad00-db2d-11eb-812f-84a5c44e88c9.png)\n\n### Dark Theme\n\nplugin use css property [prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) to define a dark theme in browser\n\n![image](https://user-images.githubusercontent.com/6692517/124240864-3dfd8180-db2c-11eb-85c1-21f0faf41775.png)\n\n## Configure\n\nThe application can be configured by editing the project's `settings.py`\nfile.\n\n| Key                               | Description                                                            | Default                                                                                                                                                                                                                                                     | Type                                                                                                                     |\n| --------------------------------- | ---------------------------------------------------------------------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------------------------ |\n| `EDITORJS_DEFAULT_PLUGINS`        | List of plugins names Editor.js from npm                               | [See above](#plugins)                                                                                                                                                                                                                                       | `list[str]`, `tuple[str]`                                                                                                |\n| `EDITORJS_DEFAULT_CONFIG_TOOLS`   | Map of Tools to use                                                    | [See above](#plugins)                                                                                                                                                                                                                                       | `dict[str, dict]`                                                                                                        |\n| `EDITORJS_IMAGE_UPLOAD_PATH`      | Path uploads images                                                    | `uploads/images/`                                                                                                                                                                                                                                           | `str`                                                                                                                    |\n| `EDITORJS_IMAGE_UPLOAD_PATH_DATE` | Subdirectories                                                         | `%Y/%m/`                                                                                                                                                                                                                                                    | `str`                                                                                                                    |\n| `EDITORJS_IMAGE_NAME_ORIGINAL`    | To use the original name of the image file?                            | `False`                                                                                                                                                                                                                                                     | `bool`                                                                                                                   |\n| `EDITORJS_IMAGE_NAME`             | Image file name. Ignored when `EDITORJS_IMAGE_NAME_ORIGINAL` is `True` | `token_urlsafe(8)`                                                                                                                                                                                                                                          | `callable(filename: str, file: InMemoryUploadedFile)` ([docs](https://docs.djangoproject.com/en/3.0/ref/files/uploads/)) |\n| `EDITORJS_EMBED_HOSTNAME_ALLOWED` | List of allowed hostname for embed                                     | `('player.vimeo.com','www.youtube.com','coub.com','vine.co','imgur.com','gfycat.com','player.twitch.tv','player.twitch.tv','music.yandex.ru','codepen.io','www.instagram.com','twitframe.com','assets.pinterest.com','www.facebook.com','www.aparat.com'),` | `list[str]`, `tuple[str]`                                                                                                |\n| `EDITORJS_VERSION`                | Version Editor.js                                                      | `2.25.0`                                                                                                                                                                                                                                                    | `str`                                                                                                                    |\n\nFor `EDITORJS_IMAGE_NAME` was used `from secrets import token_urlsafe`\n\n## Support and updates\n\nUse github issues https://github.com/2ik/django-editorjs-fields/issues\n"
  },
  {
    "path": "django_editorjs_fields/__init__.py",
    "content": "__version__ = \"0.2.7\"\n\nfrom .fields import EditorJsJSONField, EditorJsTextField\nfrom .widgets import EditorJsWidget\n\n__all__ = (\"EditorJsTextField\", \"EditorJsJSONField\", \"EditorJsWidget\", \"__version__\")\n"
  },
  {
    "path": "django_editorjs_fields/config.py",
    "content": "from secrets import token_urlsafe\n\nfrom django.conf import settings\nfrom django.urls import reverse_lazy\n\nDEBUG = getattr(settings, \"DEBUG\", False)\n\nVERSION = getattr(settings, \"EDITORJS_VERSION\", '2.25.0')\n\n# ATTACHMENT_REQUIRE_AUTHENTICATION = str(\n#     getattr(settings, \"EDITORJS_ATTACHMENT_REQUIRE_AUTHENTICATION\", True)\n# )\n\nEMBED_HOSTNAME_ALLOWED = str(\n    getattr(settings, \"EDITORJS_EMBED_HOSTNAME_ALLOWED\", (\n        'player.vimeo.com',\n        'www.youtube.com',\n        'coub.com',\n        'vine.co',\n        'imgur.com',\n        'gfycat.com',\n        'player.twitch.tv',\n        'player.twitch.tv',\n        'music.yandex.ru',\n        'codepen.io',\n        'www.instagram.com',\n        'twitframe.com',\n        'assets.pinterest.com',\n        'www.facebook.com',\n        'www.aparat.com',\n    ))\n)\n\nIMAGE_UPLOAD_PATH = str(\n    getattr(settings, \"EDITORJS_IMAGE_UPLOAD_PATH\", 'uploads/images/')\n)\n\nIMAGE_UPLOAD_PATH_DATE = getattr(\n    settings, \"EDITORJS_IMAGE_UPLOAD_PATH_DATE\", '%Y/%m/')\n\nIMAGE_NAME_ORIGINAL = getattr(\n    settings, \"EDITORJS_IMAGE_NAME_ORIGINAL\", False)\n\nIMAGE_NAME = getattr(\n    settings, \"EDITORJS_IMAGE_NAME\", lambda **_: token_urlsafe(8))\n\nPLUGINS = getattr(\n    settings, \"EDITORJS_DEFAULT_PLUGINS\", (\n        '@editorjs/paragraph',\n        '@editorjs/image',\n        '@editorjs/header',\n        '@editorjs/list',\n        '@editorjs/checklist',\n        '@editorjs/quote',\n        '@editorjs/raw',\n        '@editorjs/code',\n        '@editorjs/inline-code',\n        '@editorjs/embed',\n        '@editorjs/delimiter',\n        '@editorjs/warning',\n        '@editorjs/link',\n        '@editorjs/marker',\n        '@editorjs/table',\n    )\n)\n\nCONFIG_TOOLS = getattr(\n    settings, \"EDITORJS_DEFAULT_CONFIG_TOOLS\", {\n        'Image': {\n            'class': 'ImageTool',\n            'inlineToolbar': True,\n            \"config\": {\n                \"endpoints\": {\n                    \"byFile\": reverse_lazy('editorjs_image_upload'),\n                    \"byUrl\": reverse_lazy('editorjs_image_by_url')\n                }\n            },\n        },\n        'Header': {\n            'class': 'Header',\n            'inlineToolbar': True,\n            'config': {\n                'placeholder': 'Enter a header',\n                'levels': [2, 3, 4],\n                'defaultLevel': 2,\n            }\n        },\n        'Checklist': {'class': 'Checklist', 'inlineToolbar': True},\n        'List': {'class': 'List', 'inlineToolbar': True},\n        'Quote': {'class': 'Quote', 'inlineToolbar': True},\n        'Raw': {'class': 'RawTool'},\n        'Code': {'class': 'CodeTool'},\n        'InlineCode': {'class': 'InlineCode'},\n        'Embed': {'class': 'Embed'},\n        'Delimiter': {'class': 'Delimiter'},\n        'Warning': {'class': 'Warning', 'inlineToolbar': True},\n        'LinkTool': {\n            'class': 'LinkTool',\n            'config': {\n                # Backend endpoint for url data fetching\n                'endpoint': reverse_lazy('editorjs_linktool'),\n            }\n        },\n        'Marker': {'class': 'Marker', 'inlineToolbar': True},\n        'Table': {'class': 'Table', 'inlineToolbar': True},\n    }\n)\n\nPLUGINS_KEYS = {\n    '@editorjs/image': 'Image',\n    '@editorjs/header': 'Header',\n    '@editorjs/checklist': 'Checklist',\n    '@editorjs/list': 'List',\n    '@editorjs/quote': 'Quote',\n    '@editorjs/raw': 'Raw',\n    '@editorjs/code': 'Code',\n    '@editorjs/inline-code': 'InlineCode',\n    '@editorjs/embed': 'Embed',\n    '@editorjs/delimiter': 'Delimiter',\n    '@editorjs/warning': 'Warning',\n    '@editorjs/link': 'LinkTool',\n    '@editorjs/marker': 'Marker',\n    '@editorjs/table': 'Table',\n}\n"
  },
  {
    "path": "django_editorjs_fields/fields.py",
    "content": "import json\n\nfrom django.core import checks\nfrom django.core.exceptions import ValidationError\nfrom django.db.models import Field\nfrom django.forms import Textarea\n\nfrom .config import DEBUG, EMBED_HOSTNAME_ALLOWED\nfrom .utils import get_hostname_from_url\nfrom .widgets import EditorJsWidget\n\ntry:\n    # pylint: disable=ungrouped-imports\n    from django.db.models import JSONField  # Django >= 3.1\nexcept ImportError:\n    HAS_JSONFIELD = False\nelse:\n    HAS_JSONFIELD = True\n\n__all__ = ['EditorJsTextField', 'EditorJsJSONField']\n\n\nclass FieldMixin(Field):\n    def get_internal_type(self):\n        return 'TextField'\n\n\nclass EditorJsFieldMixin:\n    def __init__(self, plugins, tools, **kwargs):\n        self.use_editorjs = kwargs.pop('use_editorjs', True)\n        self.plugins = plugins\n        self.tools = tools\n        self.config = {}\n\n        if 'autofocus' in kwargs:\n            self.config['autofocus'] = kwargs.pop('autofocus')\n        if 'hideToolbar' in kwargs:\n            self.config['hideToolbar'] = kwargs.pop('hideToolbar')\n        if 'inlineToolbar' in kwargs:\n            self.config['inlineToolbar'] = kwargs.pop('inlineToolbar')\n        if 'readOnly' in kwargs:\n            self.config['readOnly'] = kwargs.pop('readOnly')\n        if 'minHeight' in kwargs:\n            self.config['minHeight'] = kwargs.pop('minHeight')\n        if 'logLevel' in kwargs:\n            self.config['logLevel'] = kwargs.pop('logLevel')\n        if 'placeholder' in kwargs:\n            self.config['placeholder'] = kwargs.pop('placeholder')\n        if 'defaultBlock' in kwargs:\n            self.config['defaultBlock'] = kwargs.pop('defaultBlock')\n        if 'sanitizer' in kwargs:\n            self.config['sanitizer'] = kwargs.pop('sanitizer')\n        if 'i18n' in kwargs:\n            self.config['i18n'] = kwargs.pop('i18n')\n\n        super().__init__(**kwargs)\n\n    def validate_embed(self, value):\n        for item in value.get('blocks', []):\n            type = item.get('type', '').lower()\n            if type == 'embed':\n                embed = item['data']['embed']\n                hostname = get_hostname_from_url(embed)\n\n                if hostname not in EMBED_HOSTNAME_ALLOWED:\n                    raise ValidationError(\n                        hostname + ' is not allowed in EDITORJS_EMBED_HOSTNAME_ALLOWED')\n\n    def clean(self, value, model_instance):\n        if value and value != 'null':\n            if not isinstance(value, dict):\n                try:\n                    value = json.loads(value)\n                except ValueError:\n                    pass\n                except TypeError:\n                    pass\n                else:\n                    self.validate_embed(value)\n                    value = json.dumps(value)\n            else:\n                self.validate_embed(value)\n\n        return super().clean(value, model_instance)\n\n    def formfield(self, **kwargs):\n        if self.use_editorjs:\n            kwargs['widget'] = EditorJsWidget(\n                self.plugins, self.tools, self.config, **kwargs)\n        else:\n            kwargs['widget'] = Textarea(**kwargs)\n\n        # pylint: disable=no-member\n        return super().formfield(**kwargs)\n\n\nclass EditorJsTextField(EditorJsFieldMixin, FieldMixin):\n    # pylint: disable=useless-super-delegation\n    def __init__(self, plugins=None, tools=None, **kwargs):\n        super().__init__(plugins, tools, **kwargs)\n\n    def clean(self, value, model_instance):\n        if value == 'null':\n            value = None\n\n        return super().clean(value, model_instance)\n\n\nclass EditorJsJSONField(EditorJsFieldMixin, JSONField if HAS_JSONFIELD else FieldMixin):\n    # pylint: disable=useless-super-delegation\n    def __init__(self, plugins=None, tools=None, **kwargs):\n        super().__init__(plugins, tools, **kwargs)\n\n    def check(self, **kwargs):\n        errors = super().check(**kwargs)\n        errors.extend(self._check_supported_json())\n        return errors\n\n    def _check_supported_json(self):\n        if not HAS_JSONFIELD and DEBUG:\n            return [\n                checks.Warning(\n                    'You don\\'t support JSONField, please use'\n                    'EditorJsTextField instead of EditorJsJSONField',\n                    obj=self,\n                )\n            ]\n        return []\n"
  },
  {
    "path": "django_editorjs_fields/static/django-editorjs-fields/css/django-editorjs-fields.css",
    "content": "div[data-editorjs-holder] {\n\tdisplay: inline-block;\n\twidth: 100%;\n\tmax-width: 750px;\n\tpadding: 1.5em 1em;\n\tborder: 1px solid #ccc;\n\tborder-radius: 4px;\n\tbackground-color: #fcfeff;\n}\n\n.codex-editor .ce-rawtool__textarea {\n\tbackground-color: #010e15;\n\tcolor: #ccced2;\n}\n\n.codex-editor .cdx-list {\n\tmargin: 0;\n\tpadding-left: 32px;\n\toutline: none;\n}\n\n.codex-editor .cdx-list__item {\n\tpadding: 8px;\n\tline-height: 1.4em;\n\tlist-style: inherit;\n}\n\n.codex-editor .cdx-checklist__item-text {\n\talign-self: center;\n}\n\n.codex-editor .ce-header {\n\tpadding: 1em 0;\n\tmargin: 0;\n\tmargin-bottom: -1em;\n\tline-height: 1.4em;\n\toutline: none;\n\tbackground: transparent;\n\tcolor: #000;\n\tfont-weight: 800;\n\ttext-transform: initial;\n}\n\n.codex-editor h2.ce-header {\n\tfont-size: 1.5em;\n}\n\n.codex-editor h3.ce-header {\n\tfont-size: 1.3em;\n}\n\n.codex-editor h4.ce-header {\n\tfont-size: 1.1em;\n}\n\n.codex-editor blockquote {\n\tborder: initial;\n\tmargin: initial;\n\tcolor: initial;\n\tfont-size: inherit;\n}\n\n.codex-editor .wrapper .cdx-button {\n\tdisplay: none;\n}\n\n.codex-editor .link-tool__progress {\n\tfloat: initial;\n\twidth: 100%;\n\tline-height: initial;\n\tpadding: initial;\n}\n\n@media (max-width: 767px) {\n\tdiv[data-editorjs-holder] {\n\t\twidth: auto;\n\t}\n\n\t.aligned .form-row,\n\t.aligned .form-row>div {\n\t\tflex-direction: column;\n\t}\n}\n\n@media (prefers-color-scheme: dark) {\n\n\t.tc-popover,\n\t.tc-wrap {\n\t\t--color-border: #4c6b7a !important;\n\t\t--color-background: #264b5d !important;\n\t\t--color-background-hover: #162a34 !important;\n\t\t--color-text-secondary: #fbfbfb !important;\n\t}\n\n\t.change-form #container #content-main div[data-editorjs-holder] {\n\t\tborder: 1px solid var(--border-color);\n\t\tbackground-color: var(--body-bg);\n\t}\n\n\t.change-form #container #content-main .link-tool__input {\n\t\tcolor: var(--primary);\n\t}\n\n\t.change-form #container #content-main .codex-editor .ce-header,\n\t.change-form #container #content-main .codex-editor blockquote {\n\t\tcolor: var(--body-fg);\n\t}\n\n\t.change-form #container #content-main .codex-editor .ce-rawtool__textarea {\n\t\tbackground-color: #264b5d;\n\t\tcolor: #fbfbfb;\n\t}\n\n\t.change-form #container #content-main .cdx-marker {\n\t\tbackground: #fff03b;\n\t}\n\n\t.change-form #container #content-main .ce-inline-toolbar {\n\t\tcolor: #000;\n\t}\n\n\t.change-form #container #content-main ::-moz-selection,\n\t.change-form #container #content-main ::selection {\n\t\tcolor: #fff;\n\t\tbackground: #616161;\n\t}\n\n\t.change-form #container #content-main .ce-block--selected .ce-block__content {\n\t\tbackground: #426b8a;\n\t}\n\n\t.change-form #container #content-main .codex-editor svg {\n\t\tfill: #fff\n\t}\n\n\t.change-form #container #content-main .ce-toolbar__plus:hover,\n\t.change-form #container #content-main .ce-toolbar__settings-btn:hover {\n\t\tbackground-color: #264b5d;\n\t}\n\n\t.change-form #container #content-main .ce-popover__item-icon,\n\t.change-form #container #content-main .ce-conversion-tool__icon {\n\t\tbackground: #2fa9a9;\n\t}\n\n\t.change-form #container #content-main .ce-popover,\n\t.change-form #container #content-main .ce-settings,\n\t.change-form #container #content-main .ce-inline-toolbar,\n\t.change-form #container #content-main .ce-conversion-toolbar {\n\t\tbackground-color: #264b5d;\n\t\tborder-color: #4c6b7a;\n\t\tcolor: #fbfbfb\n\t}\n\n\t.change-form #container #content-main .ce-popover__item:hover,\n\t.change-form #container #content-main .ce-settings__button:hover,\n\t.change-form #container #content-main .cdx-settings-button:hover,\n\t.change-form #container #content-main .ce-inline-toolbar__dropdown:hover,\n\t.change-form #container #content-main .ce-inline-tool:hover,\n\t.change-form #container #content-main .ce-conversion-tool:hover {\n\t\tbackground-color: #162a34;\n\t\tcolor: #fff\n\t}\n}"
  },
  {
    "path": "django_editorjs_fields/static/django-editorjs-fields/js/django-editorjs-fields.js",
    "content": ";(function () {\n  var pluginName = \"django_editorjs_fields\"\n  var pluginHelp =\n    \"Write about the issue here: https://github.com/2ik/django-editorjs-fields/issues\"\n\n  function initEditorJsPlugin() {\n    var fields = document.querySelectorAll(\"[data-editorjs-textarea]\")\n\n    for (let i = 0; i < fields.length; i++) {\n      initEditorJsField(fields[i])\n    }\n  }\n\n  function initEditorJsField(textarea) {\n    if (!textarea) {\n      logError(\"bad textarea\")\n      return false\n    }\n\n    var id = textarea.getAttribute(\"id\")\n\n    if (!id) {\n      logError(\"empty field 'id'\")\n      holder.remove()\n      return false\n    }\n\n    var holder = document.getElementById(id + \"_editorjs_holder\")\n\n    if (!holder) {\n      logError(\"holder not found\")\n      holder.remove()\n      return false\n    }\n\n    if (id.indexOf(\"__prefix__\") !== -1) return\n\n    try {\n      var config = JSON.parse(textarea.getAttribute(\"data-config\"))\n    } catch (error) {\n      console.error(error)\n      logError(\n        \"invalid 'data-config' on field: \" + id + \" . Clear the field manually\"\n      )\n      holder.remove()\n      return false\n    }\n\n    var text = textarea.value.trim()\n\n    if (text) {\n      try {\n        text = JSON.parse(text)\n      } catch (error) {\n        console.error(error)\n        logError(\n          \"invalid json data from the database. Clear the field manually\"\n        )\n        holder.remove()\n        return false\n      }\n    }\n\n    textarea.style.display = \"none\" // remove old textarea\n\n    var editorConfig = {\n      id: id,\n      holder: holder,\n      data: text,\n    }\n\n    if (\"tools\" in config) {\n      // set config\n      var tools = config.tools\n\n      for (var plugin in tools) {\n        var cls = tools[plugin].class\n\n        if (cls && window[cls] != undefined) {\n          tools[plugin].class = eval(cls)\n          continue\n        }\n\n        delete tools[plugin]\n        logError(\"[\" + plugin + \"] Class \" + cls + \" Not Found\")\n      }\n\n      editorConfig.tools = tools\n    }\n\n    if (\"autofocus\" in config) {\n      editorConfig.autofocus = !!config.autofocus\n    }\n\n    if (\"hideToolbar\" in config) {\n      editorConfig.hideToolbar = !!config.hideToolbar\n    }\n\n    if (\"inlineToolbar\" in config) {\n      editorConfig.inlineToolbar = config.inlineToolbar\n    }\n\n    if (\"readOnly\" in config) {\n      editorConfig.readOnly = config.readOnly\n    }\n\n    if (\"minHeight\" in config) {\n      editorConfig.minHeight = config.minHeight || 300\n    }\n\n    if (\"logLevel\" in config) {\n      editorConfig.logLevel = config.logLevel || \"VERBOSE\"\n    } else {\n      editorConfig.logLevel = \"ERROR\"\n    }\n\n    if (\"placeholder\" in config) {\n      editorConfig.placeholder = config.placeholder || \"Type text...\"\n    } else {\n      editorConfig.placeholder = \"Type text...\"\n    }\n\n    if (\"defaultBlock\" in config) {\n      editorConfig.defaultBlock = config.defaultBlock || \"paragraph\"\n    }\n\n    if (\"sanitizer\" in config) {\n      editorConfig.sanitizer = config.sanitizer || {\n        p: true,\n        b: true,\n        a: true,\n      }\n    }\n\n    if (\"i18n\" in config) {\n      editorConfig.i18n = config.i18n || {}\n    }\n\n    editorConfig.onChange = function () {\n      editor\n        .save()\n        .then(function (data) {\n          if (data.blocks.length) {\n            textarea.value = JSON.stringify(data)\n          } else {\n            textarea.value = 'null'\n          }\n        })\n        .catch(function (error) {\n          console.log(\"save error: \", error)\n        })\n    }\n    var editor = new EditorJS(editorConfig)\n    holder.setAttribute(\"data-processed\", 1)\n  }\n\n  function logError(msg) {\n    console.error(pluginName + \" - \" + msg + \". \" + pluginHelp)\n  }\n\n  addEventListener(\"DOMContentLoaded\", initEditorJsPlugin)\n\n  // Event\n  if (typeof django === \"object\" && django.jQuery) {\n    django.jQuery(document).on(\"formset:added\", function (event, $row) {\n      var areas = $row.find(\"[data-editorjs-textarea]\").get()\n\n      if (areas) {\n        for (let i = 0; i < areas.length; i++) {\n          initEditorJsField(areas[i])\n        }\n      }\n    })\n  }\n})()\n"
  },
  {
    "path": "django_editorjs_fields/templates/django-editorjs-fields/widget.html",
    "content": "<textarea data-editorjs-textarea name=\"{{ widget.name }}\"{% include \"django/forms/widgets/attrs.html\" %} data-config='{{ widget.config }}'>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>\n<div data-editorjs-holder id=\"{{ widget.attrs.id }}_editorjs_holder\" class=\"editorjs-holder\"></div>"
  },
  {
    "path": "django_editorjs_fields/templatetags/__init__.py",
    "content": ""
  },
  {
    "path": "django_editorjs_fields/templatetags/editorjs.py",
    "content": "import json\n\nfrom django import template\nfrom django.utils.safestring import mark_safe\n\nregister = template.Library()\n\n\ndef generate_paragraph(data):\n    text = data.get('text').replace('&nbsp;', ' ')\n    return f'<p>{text}</p>'\n\n\ndef generate_list(data):\n    list_li = ''.join([f'<li>{item}</li>' for item in data.get('items')])\n    tag = 'ol' if data.get('style') == 'ordered' else 'ul'\n    return f'<{tag}>{list_li}</{tag}>'\n\n\ndef generate_header(data):\n    text = data.get('text').replace('&nbsp;', ' ')\n    level = data.get('level')\n    return f'<h{level}>{text}</h{level}>'\n\n\ndef generate_image(data):\n    url = data.get('file', {}).get('url')\n    caption = data.get('caption')\n    classes = []\n\n    if data.get('stretched'):\n        classes.append('stretched')\n    if data.get('withBorder'):\n        classes.append('withBorder')\n    if data.get('withBackground'):\n        classes.append('withBackground')\n\n    classes = ' '.join(classes)\n\n    return f'<img src=\"{url}\" alt=\"{caption}\" class=\"{classes}\" />'\n\n\ndef generate_delimiter():\n    return '<div class=\"delimiter\"></div>'\n\n\ndef generate_table(data):\n    rows = data.get('content', [])\n    table = ''\n\n    for row in rows:\n        table += '<tr>'\n        for cell in row:\n            table += f'<td>{cell}</td>'\n        table += '</tr>'\n\n    return f'<table>{table}</table>'\n\n\ndef generate_warning(data):\n    title, message = data.get('title'), data.get('message')\n\n    if title:\n        title = f'<div class=\"alert__title\">{title}</div>'\n    if message:\n        message = f'<div class=\"alert__message\">{message}</div>'\n\n    return f'<div class=\"alert\">{title}{message}</div>'\n\n\ndef generate_quote(data):\n    alignment = data.get('alignment')\n    caption = data.get('caption')\n    text = data.get('text')\n\n    if caption:\n        caption = f'<cite>{caption}</cite>'\n\n    classes = f'align-{alignment}' if alignment else None\n\n    return f'<blockquote class=\"{classes}\">{text}{caption}</blockquote>'\n\n\ndef generate_code(data):\n    code = data.get('code')\n    return f'<code class=\"code\">{code}</code>'\n\n\ndef generate_raw(data):\n    return data.get('html')\n\n\ndef generate_embed(data):\n    service = data.get('service')\n    caption = data.get('caption')\n    embed = data.get('embed')\n    iframe = f'<iframe src=\"{embed}\" allow=\"autoplay\" allowfullscreen=\"allowfullscreen\"></iframe>'\n\n    return f'<div class=\"embed {service}\">{iframe}{caption}</div>'\n\n\ndef generate_link(data):\n\n    link, meta = data.get('link'), data.get('meta')\n\n    if not link or not meta:\n        return ''\n\n    title = meta.get('title')\n    description = meta.get('description')\n    image = meta.get('image')\n\n    wrapper = f'<div class=\"link-block\"><a href=\"{ link }\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">'\n\n    if image.get('url'):\n        image_url = image.get('url')\n        wrapper += f'<div class=\"link-block__image\" style=\"background-image: url(\\'{image_url}\\');\"></div>'\n\n    if title:\n        wrapper += f'<p class=\"link-block__title\">{title}</p>'\n\n    if description:\n        wrapper += f'<p class=\"link-block__description\">{description}</p>'\n\n    wrapper += f'<p class=\"link-block__link\">{link}</p>'\n    wrapper += '</a></div>'\n    return wrapper\n\n\n@register.filter(is_safe=True)\ndef editorjs(value):\n    if not value or value == 'null':\n        return \"\"\n\n    if not isinstance(value, dict):\n        try:\n            value = json.loads(value)\n        except ValueError:\n            return value\n        except TypeError:\n            return value\n\n    html_list = []\n    for item in value['blocks']:\n\n        type, data = item.get('type'), item.get('data')\n        type = type.lower()\n\n        if type == 'paragraph':\n            html_list.append(generate_paragraph(data))\n        elif type == 'header':\n            html_list.append(generate_header(data))\n        elif type == 'list':\n            html_list.append(generate_list(data))\n        elif type == 'image':\n            html_list.append(generate_image(data))\n        elif type == 'delimiter':\n            html_list.append(generate_delimiter())\n        elif type == 'warning':\n            html_list.append(generate_warning(data))\n        elif type == 'table':\n            html_list.append(generate_table(data))\n        elif type == 'code':\n            html_list.append(generate_code(data))\n        elif type == 'raw':\n            html_list.append(generate_raw(data))\n        elif type == 'embed':\n            html_list.append(generate_embed(data))\n        elif type == 'quote':\n            html_list.append(generate_quote(data))\n        elif type == 'linktool':\n            html_list.append(generate_link(data))\n\n    return mark_safe(''.join(html_list))\n"
  },
  {
    "path": "django_editorjs_fields/urls.py",
    "content": "from django.contrib.admin.views.decorators import staff_member_required\nfrom django.urls import path\n\nfrom .views import ImageByUrl, ImageUploadView, LinkToolView\n\nurlpatterns = [\n    path(\n        'image_upload/',\n        staff_member_required(ImageUploadView.as_view()),\n        name='editorjs_image_upload',\n    ),\n    path(\n        'linktool/',\n        staff_member_required(LinkToolView.as_view()),\n        name='editorjs_linktool',\n    ),\n    path(\n        'image_by_url/',\n        ImageByUrl.as_view(),\n        name='editorjs_image_by_url',\n    ),\n]\n"
  },
  {
    "path": "django_editorjs_fields/utils.py",
    "content": "import urllib.parse\n\nfrom django.conf import settings\nfrom django.utils.module_loading import import_string\n\n\ndef get_storage_class():\n    return import_string(\n        getattr(\n            settings,\n            'EDITORJS_STORAGE_BACKEND',\n            'django.core.files.storage.DefaultStorage',\n        )\n    )()\n\n\ndef get_hostname_from_url(url):\n    obj_url = urllib.parse.urlsplit(url)\n    return obj_url.hostname\n\n\nstorage = get_storage_class()\n"
  },
  {
    "path": "django_editorjs_fields/views.py",
    "content": "import json\nimport logging\nimport os\nfrom datetime import datetime\nfrom urllib.error import HTTPError, URLError\nfrom urllib.parse import urlencode\nfrom urllib.request import Request, urlopen\n\n# from django.conf import settings\nfrom django.core.exceptions import ValidationError\nfrom django.core.validators import URLValidator\nfrom django.http import JsonResponse\nfrom django.utils.decorators import method_decorator\nfrom django.views import View\nfrom django.views.decorators.csrf import csrf_exempt\n\nfrom .config import (IMAGE_NAME, IMAGE_NAME_ORIGINAL, IMAGE_UPLOAD_PATH,\n                     IMAGE_UPLOAD_PATH_DATE)\nfrom .utils import storage\n\nLOGGER = logging.getLogger('django_editorjs_fields')\n\n\nclass ImageUploadView(View):\n    http_method_names = [\"post\"]\n    # http_method_names = [\"post\", \"delete\"]\n\n    @method_decorator(csrf_exempt)\n    def dispatch(self, request, *args, **kwargs):\n        return super().dispatch(request, *args, **kwargs)\n\n    def post(self, request):\n        if 'image' in request.FILES:\n            the_file = request.FILES['image']\n            allowed_types = [\n                'image/jpeg',\n                'image/jpg',\n                'image/pjpeg',\n                'image/x-png',\n                'image/png',\n                'image/webp',\n                'image/gif',\n            ]\n            if the_file.content_type not in allowed_types:\n                return JsonResponse(\n                    {'success': 0, 'message': 'You can only upload images.'}\n                )\n\n            filename, extension = os.path.splitext(the_file.name)\n\n            if IMAGE_NAME_ORIGINAL is False:\n                filename = IMAGE_NAME(filename=filename, file=the_file)\n\n            filename += extension\n\n            upload_path = IMAGE_UPLOAD_PATH\n\n            if IMAGE_UPLOAD_PATH_DATE:\n                upload_path += datetime.now().strftime(IMAGE_UPLOAD_PATH_DATE)\n\n            path = storage.save(\n                os.path.join(upload_path, filename), the_file\n            )\n            link = storage.url(path)\n\n            return JsonResponse({'success': 1, 'file': {\"url\": link}})\n        return JsonResponse({'success': 0})\n\n    # def delete(self, request):\n    #     path_file = request.GET.get('pathFile')\n\n    #     if not path_file:\n    #         return JsonResponse({'success': 0, 'message': 'Parameter \"pathFile\" Not Found'})\n\n    #     base_dir = getattr(settings, \"BASE_DIR\", '')\n    #     path_file = f'{base_dir}{path_file}'\n\n    #     if not os.path.isfile(path_file):\n    #         return JsonResponse({'success': 0, 'message': 'File Not Found'})\n\n    #     os.remove(path_file)\n\n    #     return JsonResponse({'success': 1})\n\n\nclass LinkToolView(View):\n    http_method_names = [\"get\"]\n\n    @method_decorator(csrf_exempt)\n    def dispatch(self, request, *args, **kwargs):\n        return super().dispatch(request, *args, **kwargs)\n\n    def get(self, request):\n\n        url = request.GET.get('url', '')\n\n        LOGGER.debug('Starting to get meta for: %s', url)\n\n        if not any([url.startswith(s) for s in ('http://', 'https://')]):\n            LOGGER.debug('Adding the http protocol to the link: %s', url)\n            url = 'http://' + url\n\n        validate = URLValidator(schemes=['http', 'https'])\n\n        try:\n            validate(url)\n        except ValidationError as e:\n            LOGGER.error(e)\n        else:\n            try:\n                LOGGER.debug('Let\\'s try to get meta from: %s', url)\n\n                full_url = 'https://api.microlink.io/?' + \\\n                    urlencode({'url': url})\n\n                req = Request(full_url, headers={\n                    'User-Agent': request.META.get('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)')\n                })\n                res = urlopen(req)\n            except HTTPError as e:\n                LOGGER.error('The server couldn\\'t fulfill the request.')\n                LOGGER.error('Error code: %s %s', e.code, e.msg)\n            except URLError as e:\n                LOGGER.error('We failed to reach a server. url: %s', url)\n                LOGGER.error('Reason: %s', e.reason)\n            else:\n                res_body = res.read()\n                res_json = json.loads(res_body.decode(\"utf-8\"))\n\n                if 'success' in res_json.get('status'):\n                    data = res_json.get('data')\n\n                    if data:\n                        LOGGER.debug('Response meta: %s', data)\n                        meta = {}\n                        meta['title'] = data.get('title')\n                        meta['description'] = data.get('description')\n                        meta['image'] = data.get('image')\n\n                        return JsonResponse({\n                            'success': 1,\n                            'link': data.get('url', url),\n                            'meta': meta\n                        })\n\n        return JsonResponse({'success': 0})\n\n\nclass ImageByUrl(View):\n    http_method_names = [\"post\"]\n\n    @method_decorator(csrf_exempt)\n    def dispatch(self, request, *args, **kwargs):\n        return super().dispatch(request, *args, **kwargs)\n\n    def post(self, request):\n        body = json.loads(request.body.decode())\n        if 'url' in body:\n            return JsonResponse({'success': 1, 'file': {\"url\": body['url']}})\n        return JsonResponse({'success': 0})\n"
  },
  {
    "path": "django_editorjs_fields/widgets.py",
    "content": "import json\n\nfrom django.core.serializers.json import DjangoJSONEncoder\nfrom django.forms import Media, widgets\nfrom django.forms.renderers import get_default_renderer\nfrom django.utils.encoding import force_str\nfrom django.utils.functional import Promise, cached_property\nfrom django.utils.html import conditional_escape\nfrom django.utils.safestring import mark_safe\n\nfrom .config import CONFIG_TOOLS, PLUGINS, PLUGINS_KEYS, VERSION\n\n\nclass LazyEncoder(DjangoJSONEncoder):\n    def default(self, obj):\n        if isinstance(obj, Promise):\n            return force_str(obj)\n        return super().default(obj)\n\n\njson_encode = LazyEncoder().encode\n\n\nclass EditorJsWidget(widgets.Textarea):\n    def __init__(self, plugins=None, tools=None, config=None, **kwargs):\n        self.plugins = plugins\n        self.tools = tools\n        self.config = config\n\n        # Fix \"__init__() got an unexpected keyword argument 'widget'\"\n        widget = kwargs.pop('widget', None)\n        if widget:\n            self.plugins = widget.plugins\n            self.tools = widget.tools\n            self.config = widget.config\n\n        super().__init__(**kwargs)\n\n    def configuration(self):\n        tools = {}\n        config = self.config or {}\n\n        if self.plugins or self.tools:\n            custom_tools = self.tools or {}\n            # get name packages without version\n            plugins = ['@'.join(p.split('@')[:2])\n                       for p in self.plugins or PLUGINS]\n\n            for plugin in plugins:\n                plugin_key = PLUGINS_KEYS.get(plugin)\n\n                if not plugin_key:\n                    continue\n\n                plugin_tools = custom_tools.get(\n                    plugin_key) or CONFIG_TOOLS.get(plugin_key) or {}\n                plugin_class = plugin_tools.get('class')\n\n                if plugin_class:\n\n                    tools[plugin_key] = custom_tools.get(\n                        plugin_key, CONFIG_TOOLS.get(plugin_key)\n                    )\n\n                    tools[plugin_key]['class'] = plugin_class\n\n                    custom_tools.pop(plugin_key, None)\n\n            if custom_tools:\n                tools.update(custom_tools)\n        else:  # default\n            tools.update(CONFIG_TOOLS)\n\n        config.update(tools=tools)\n        return config\n\n    @cached_property\n    def media(self):\n        js_list = [\n            '//cdn.jsdelivr.net/npm/@editorjs/editorjs@' + VERSION  # lib\n        ]\n\n        plugins = self.plugins or PLUGINS\n\n        if plugins:\n            js_list += ['//cdn.jsdelivr.net/npm/' + p for p in plugins]\n\n        js_list.append('django-editorjs-fields/js/django-editorjs-fields.js')\n\n        return Media(\n            js=js_list,\n            css={\n                'all': [\n                    'django-editorjs-fields/css/django-editorjs-fields.css'\n                ]\n            },\n        )\n\n    def render(self, name, value, attrs=None, renderer=None):\n        if value is None:\n            value = ''\n\n        if renderer is None:\n            renderer = get_default_renderer()\n\n        return mark_safe(renderer.render(\"django-editorjs-fields/widget.html\", {\n            'widget': {\n                'name': name,\n                'value': conditional_escape(force_str(value)),\n                'attrs': self.build_attrs(self.attrs, attrs),\n                'config': json_encode(self.configuration()),\n            }\n        }))\n"
  },
  {
    "path": "example/.gitignore",
    "content": "media/\n*.log"
  },
  {
    "path": "example/blog/__init__.py",
    "content": ""
  },
  {
    "path": "example/blog/admin.py",
    "content": "from django.contrib import admin\nfrom blog.models import Post, Comment\n\n\nclass CommentInline(admin.TabularInline):\n    model = Comment\n    extra = 0\n\n    fields = ('content',)\n\n\n@admin.register(Post)\nclass PostAdmin(admin.ModelAdmin):\n    inlines = [\n        CommentInline,\n    ]\n"
  },
  {
    "path": "example/blog/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass BlogConfig(AppConfig):\n    name = 'blog'\n"
  },
  {
    "path": "example/blog/forms.py",
    "content": "from django import forms\nfrom django_editorjs_fields import EditorJsWidget\n\nfrom .models import Post\n\n\nclass TestForm(forms.ModelForm):\n    # body_editorjs = EditorJsWidget(config={\"minHeight\": 100, 'autofocus': False})\n\n    # inputs = forms.JSONField(widget=EditorJsWidget())\n    # inputs.widget.config = {\"minHeight\": 100}\n\n    class Meta:\n        model = Post\n        exclude = []\n        widgets = {\n            'body_editorjs': EditorJsWidget(config={'minHeight': 100}),\n            'body_textfield': EditorJsWidget(plugins=[\n                \"@editorjs/image\",\n                \"@editorjs/header\"\n            ], config={'minHeight': 100})\n        }\n"
  },
  {
    "path": "example/blog/migrations/0001_initial.py",
    "content": "# Generated by Django 3.1.2 on 2020-10-15 14:14\n\nfrom django.db import migrations, models\nimport django_editorjs_fields.fields\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Post',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('body_default', models.TextField()),\n                ('body_editorjs', django_editorjs_fields.fields.EditorJsJSONField()),\n                ('body_custom', django_editorjs_fields.fields.EditorJsJSONField(blank=True, null=True)),\n                ('body_textfield', django_editorjs_fields.fields.EditorJsTextField(blank=True, null=True)),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "example/blog/migrations/0002_comment.py",
    "content": "# Generated by Django 3.2.7 on 2021-09-22 14:33\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\nimport django_editorjs_fields.fields\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('blog', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Comment',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('content', django_editorjs_fields.fields.EditorJsJSONField(blank=True, null=True)),\n                ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='blog.post')),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "example/blog/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "example/blog/models.py",
    "content": "from django.db import models\nfrom django.urls import reverse\nfrom django_editorjs_fields import EditorJsJSONField, EditorJsTextField\n\n\nclass Post(models.Model):\n    body_default = models.TextField()\n    body_editorjs = EditorJsJSONField(readOnly=False, autofocus=True)\n    body_custom = EditorJsJSONField(\n        plugins=[\n            \"@editorjs/image\",\n            \"@editorjs/header\",\n            \"@editorjs/list\",\n            \"editorjs-github-gist-plugin\",\n            \"editorjs-hyperlink\",\n            \"@editorjs/code\",\n            \"@editorjs/inline-code\",\n            \"@editorjs/table@1.3.0\",\n        ],\n        tools={\n            \"Gist\": {\n                \"class\": \"Gist\"\n            },\n            \"Hyperlink\": {\n                \"class\": \"Hyperlink\",\n                \"config\": {\n                    \"shortcut\": 'CMD+L',\n                    \"target\": '_blank',\n                    \"rel\": 'nofollow',\n                    \"availableTargets\": ['_blank', '_self'],\n                    \"availableRels\": ['author', 'noreferrer'],\n                    \"validate\": False,\n                }\n            },\n            \"Image\": {\n                'class': 'ImageTool',\n                \"config\": {\n                    \"endpoints\": {\n                        # Your custom backend file uploader endpoint\n                        \"byFile\": \"/editorjs/image_upload/\"\n                    }\n                }\n            }\n        },\n        null=True,\n        blank=True,\n    )\n    body_textfield = EditorJsTextField(  # only images and paragraph (default)\n        plugins=[\"@editorjs/image\", \"@editorjs/embed\"], null=True, blank=True,\n        i18n={\n            'messages': {\n                'blockTunes': {\n                    \"delete\": {\n                        \"Delete\": \"Удалить\"\n                    },\n                    \"moveUp\": {\n                        \"Move up\": \"Переместить вверх\"\n                    },\n                    \"moveDown\": {\n                        \"Move down\": \"Переместить вниз\"\n                    }\n                }\n            },\n        }\n    )\n\n    def get_absolute_url(self):\n        return reverse('post_detail', kwargs={'pk': self.id})\n\n    def __str__(self):\n        return '{}'.format(self.id)\n\n\nclass Comment(models.Model):\n    content = EditorJsJSONField(null=True, blank=True)\n    post = models.ForeignKey(\n        'Post', related_name='comments', on_delete=models.CASCADE)\n"
  },
  {
    "path": "example/blog/templates/blog/post_update.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Document</title>\n</head>\n<body>\n{% if form %}\n    <!-- Form Errors -->\n    {% if form.errors %}\n        <ul class=\"errors\">\n            {% for error in form.errors %}\n                <li>{{ error }}</li>\n            {% endfor %}\n        </ul>\n    {% endif %}\n\n    <!-- Display Form -->\n    <form method=\"post\">\n    {% csrf_token %}\n    {% for field in form %}\n        <div class=\"mydiv\">\n            <label class=\"mylabel\">{{ field.label }}</label>\n            {{ field }}\n        </div>\n    {% endfor %}\n        <button type=\"submit\">Submit</button>\n    </form>\n{% endif %}\n\n{{ form.media }}\n</body>\n</html>\n"
  },
  {
    "path": "example/blog/templates/blog/post_view.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Document</title>\n</head>\n<body>\n{% load editorjs %}\n{{ post.body_editorjs | editorjs}}\n{{ post.body_custom | editorjs}}\n{{ post.body_textfield | editorjs}}\n\n</body>\n</html>\n"
  },
  {
    "path": "example/blog/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "example/blog/urls.py",
    "content": "from django.urls import path\n\nfrom .views import PostUpdate, PostView\n\nurlpatterns = [\n    path('posts/<int:pk>/edit', PostUpdate.as_view(), name='post_edit'),\n    path('posts/<int:pk>', PostView.as_view(), name='post_detail'),\n]\n"
  },
  {
    "path": "example/blog/views.py",
    "content": "from django.shortcuts import redirect, render\nfrom django.views import View\n\nfrom .forms import TestForm\nfrom .models import Post\n\n\nclass PostUpdate(View):\n    def get(self, request, pk):\n        post = Post.objects.get(id=pk)\n        bound_form = TestForm(instance=post)\n        return render(request, 'blog/post_update.html', {'form': bound_form, 'post': post})\n\n    def post(self, request, pk):\n        post = Post.objects.get(id=pk)\n        bound_form = TestForm(request.POST, instance=post)\n\n        if bound_form.is_valid():\n            new_post = bound_form.save()\n            return redirect(new_post)\n        return render(request, 'blog/post_update.html', {'form': bound_form, 'post': post})\n\n\nclass PostView(View):\n    def get(self, request, pk):\n        post = Post.objects.get(id=pk)\n        return render(request, 'blog/post_view.html', {'post': post})\n"
  },
  {
    "path": "example/example/__init__.py",
    "content": ""
  },
  {
    "path": "example/example/asgi.py",
    "content": "\"\"\"\nASGI config for example project.\n\nIt exposes the ASGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/3.1/howto/deployment/asgi/\n\"\"\"\n\nimport os\n\nfrom django.core.asgi import get_asgi_application\n\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')\n\napplication = get_asgi_application()\n"
  },
  {
    "path": "example/example/settings.py",
    "content": "\"\"\"\nDjango settings for example project.\n\nGenerated by 'django-admin startproject' using Django 3.1.2.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/3.1/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/3.1/ref/settings/\n\"\"\"\n\nfrom pathlib import Path\n\n# Build paths inside the project like this: BASE_DIR / 'subdir'.\nBASE_DIR = Path(__file__).resolve().parent.parent\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = '9yx!8#ocntj+t40#&2+qj&+)n1ajvx_-mo5247&evr*)37=y_x'\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = True\n\nALLOWED_HOSTS = []\n\nDEFAULT_AUTO_FIELD = 'django.db.models.AutoField'\n\n# Application definition\n\nDEFAULT_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n]\n\nINSTALLED_APPS = DEFAULT_APPS + [\n    'blog.apps.BlogConfig',\n    'django_editorjs_fields',\n]\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\nROOT_URLCONF = 'example.urls'\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = 'example.wsgi.application'\n\n\n# Database\n# https://docs.djangoproject.com/en/3.1/ref/settings/#databases\n\nDATABASES = {\n    # 'default': {\n    #     'ENGINE': 'django.db.backends.sqlite3',\n    #     'NAME': BASE_DIR / 'db.sqlite3',\n    # }\n    'default': {\n        'ENGINE': 'django.db.backends.postgresql',\n        'NAME': 'test_django',\n        'USER': 'postgres',\n        'PASSWORD': 'postgres',\n        'HOST': '127.0.0.1',\n        'PORT': '5432',\n    }\n}\n\nLOGGING = {\n    'version': 1,\n    'disable_existing_loggers': False,\n    'formatters': {\n        'standard': {\n            'format': '%(asctime)s %(filename)s:%(lineno)d %(levelname)s - %(message)s'\n        },\n    },\n    'handlers': {\n        'console': {\n            'class': 'logging.StreamHandler',\n        },\n        'django_editorjs_fields': {\n            'level': 'DEBUG',\n            'class': 'logging.handlers.RotatingFileHandler',\n            'filename': 'django_editorjs_fields.log',\n            'maxBytes': 1024*1024*5,  # 5 MB\n            'backupCount': 5,\n            'formatter': 'standard',\n        },\n    },\n    'loggers': {\n        'django_editorjs_fields': {\n            'handlers': ['django_editorjs_fields', 'console'],\n            'level': 'DEBUG',\n        },\n    },\n}\n\n\n# Password validation\n# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/3.1/topics/i18n/\n\nLANGUAGE_CODE = 'en-us'\n\nTIME_ZONE = 'UTC'\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/3.1/howto/static-files/\n\nSTATIC_ROOT = BASE_DIR / \"static/\"\nSTATIC_URL = '/static/'\n\nMEDIA_ROOT = BASE_DIR / \"media\"\nMEDIA_URL = \"/media/\"\n\n# django_editorjs_fields\nEDITORJS_VERSION = '2.25.0'\n# EDITORJS_IMAGE_NAME_ORIGINAL = True\n# EDITORJS_IMAGE_UPLOAD_PATH_DATE = None\n# EDITORJS_IMAGE_NAME = lambda filename, **_: f\"{filename}_12345\"\n"
  },
  {
    "path": "example/example/urls.py",
    "content": "\"\"\"example URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://docs.djangoproject.com/en/3.1/topics/http/urls/\nExamples:\nFunction views\n    1. Add an import:  from my_app import views\n    2. Add a URL to urlpatterns:  path('', views.home, name='home')\nClass-based views\n    1. Add an import:  from other_app.views import Home\n    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')\nIncluding another URLconf\n    1. Import the include() function: from django.urls import include, path\n    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))\n\"\"\"\nfrom django.contrib import admin\nfrom django.urls import path, include\nfrom django.conf import settings\nfrom django.conf.urls.static import static\n\nurlpatterns = [\n    path('admin/', admin.site.urls),\n    path('editorjs/', include('django_editorjs_fields.urls')),\n    path('', include('blog.urls')),\n] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)\n"
  },
  {
    "path": "example/example/wsgi.py",
    "content": "\"\"\"\nWSGI config for example project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/\n\"\"\"\n\nimport os\n\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')\n\napplication = get_wsgi_application()\n"
  },
  {
    "path": "example/manage.py",
    "content": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\nimport os\nimport sys\n\n\ndef main():\n    \"\"\"Run administrative tasks.\"\"\"\n    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError as exc:\n        raise ImportError(\n            \"Couldn't import Django. Are you sure it's installed and \"\n            \"available on your PYTHONPATH environment variable? Did you \"\n            \"forget to activate a virtual environment?\"\n        ) from exc\n    execute_from_command_line(sys.argv)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"django-editorjs-fields\"\nversion = \"0.2.7\"\ndescription = \"Django plugin for using Editor.js\"\nauthors = [\"Ilya Kotlyakov <m@2ik.ru>\"]\nlicense = \"MIT\"\nrepository = \"https://github.com/2ik/django-editorjs-fields\"\ndocumentation = \"https://github.com/2ik/django-editorjs-fields\"\nreadme = \"README.md\"\nkeywords = [\"editorjs\", \"django-editor\", \"django-wysiwyg\", \"wysiwyg\", \"django-admin\"]\n\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Operating System :: OS Independent\",\n    \"Intended Audience :: Developers\",\n    \"Framework :: Django\",\n    \"Framework :: Django :: 2.2\",\n    \"Framework :: Django :: 3.0\",\n    \"Framework :: Django :: 3.1\",\n    \"Framework :: Django :: 3.2\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Programming Language :: Python :: 3.6\",\n    \"Programming Language :: Python :: 3.7\",\n    \"Programming Language :: Python :: 3.8\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Topic :: Software Development :: Libraries :: Application Frameworks\",\n    \"Topic :: Software Development :: Libraries :: Python Modules\",\n]\n\ninclude = [\n    \"LICENSE\",\n]\n\n[tool.poetry.dependencies]\npython = \"^3.6\"\n\n[tool.poetry.dev-dependencies]\nDjango = \"^3.1.0\"\npylint = \"^2.6.0\"\nautopep8 = \"^1.5.4\"\npylint-django = \"^2.3.0\"\n\n[build-system]\nrequires = [\"poetry>=0.12\"]\nbuild-backend = \"poetry.masonry.api\"\n"
  }
]