+
{{ markdownx_editor }}
-
-
{% trans "Preview" %}
-
+
```
# Dependencies
-* jQuery – AJAX upload and JS functionality
-
-# TODO
-
-* custom URL upload link
-* custom media path function
-* python 3 compatibility
-* tests
+* Markdown
+* Pillow
+* jQuery
# Changelog
-### v0.4.2
+###### v1.0
+
+* Warning: no backward compatibility
+* Admin, Model and Form custom objects
+* Django admin styles for compiled markdown
+* Settings variables changed:
+ * MARKDOWNX_MAX_SIZE => MARKDOWNX_IMAGE_MAX_SIZE
+ * MARKDOWNX_MARKDOWN_KWARGS => MARKDOWNX_MARKDOWN_EXTENSIONS
+ * MARKDOWNX_MAX_UPLOADSIZE => MARKDOWNX_UPLOAD_MAX_SIZE
+ * MARKDOWNX_CONTENT_TYPES => MARKDOWNX_UPLOAD_CONTENT_TYPES
+
+###### v0.4.2
* Path fix by argaen
-### v0.4.1
+###### v0.4.1
* Better editor height updates
* Refresh preview on image upload
* Small JS code fixes
-### v0.4.0
+###### v0.4.0
* editor auto height
-### v0.3.1
+###### v0.3.1
* JS event fix
-### v0.3.0
+###### v0.3.0
* version bump
-### v0.2.9
+###### v0.2.9
* Removed any inlcuded css
* Removed JS markdown compiler (full python support now with Markdown lib)
-### v0.2.0
+###### v0.2.0
* Allow to paste tabs using Tab button
-### v0.1.4
+###### v0.1.4
* package data fix
-### v0.1.3
+###### v0.1.3
* README.md fix on PyPi
-### v0.1.2
+###### v0.1.2
* critical setuptools fix
-### v0.1.1
+###### v0.1.1
* change context name `editor` to `markdownx_editor` for better consistency
-### v0.1.0
+###### v0.1.0
* init
+
+# License
+
+django-markdown is licensed under the open source BSD license
+
+
+# TODO
+
+* python 3 compatibility
+* tests
+
+Would be nice to have some help with those!
+
+
# Notes
**django-markdownx** was inspired by great [django-images](https://github.com/mirumee/django-images) and [django-bootstrap-markdown](http://thegoods.aj7may.com/django-bootstrap-markdown/) packages.
diff --git a/django-markdownx-preview.gif b/django-markdownx-preview.gif
new file mode 100644
index 0000000..9815b5c
Binary files /dev/null and b/django-markdownx-preview.gif differ
diff --git a/markdownx/admin.py b/markdownx/admin.py
new file mode 100644
index 0000000..b8e07e0
--- /dev/null
+++ b/markdownx/admin.py
@@ -0,0 +1,12 @@
+from django.contrib import admin
+from django.db import models
+
+from .widgets import AdminMarkdownxWidget
+from .models import MarkdownxField
+
+
+class MarkdownxModelAdmin(admin.ModelAdmin):
+
+ formfield_overrides = {
+ MarkdownxField: {'widget': AdminMarkdownxWidget}
+ }
diff --git a/markdownx/fields.py b/markdownx/fields.py
new file mode 100644
index 0000000..074b93d
--- /dev/null
+++ b/markdownx/fields.py
@@ -0,0 +1,23 @@
+from django import forms
+
+from .settings import MARKDOWNX_EDITOR_RESIZABLE
+from .widgets import (
+ MarkdownxWidget,
+ AdminMarkdownxWidget,
+)
+
+
+class MarkdownxFormField(forms.CharField):
+
+ def __init__(self, *args, **kwargs):
+ super(MarkdownxFormField, self).__init__(*args, **kwargs)
+
+ if self.widget.__class__ != AdminMarkdownxWidget:
+ self.widget = MarkdownxWidget()
+
+ if self.widget.attrs.has_key('class'):
+ self.widget.attrs['class'] += ' markdownx-editor'
+ else:
+ self.widget.attrs.update({'class':'markdownx-editor'})
+
+ self.widget.attrs['data-markdownx-editor-resizable'] = MARKDOWNX_EDITOR_RESIZABLE
diff --git a/markdownx/forms.py b/markdownx/forms.py
index ab82efe..6a7fc5b 100755
--- a/markdownx/forms.py
+++ b/markdownx/forms.py
@@ -10,28 +10,30 @@ from django.template import defaultfilters as filters
from .utils import scale_and_crop
from .settings import (
- MARKDOWNX_IMAGE_SIZE,
+ MARKDOWNX_IMAGE_MAX_SIZE,
MARKDOWNX_MEDIA_PATH,
- MARKDOWNX_CONTENT_TYPES,
- MARKDOWNX_MAX_UPLOAD_SIZE,
+ MARKDOWNX_UPLOAD_CONTENT_TYPES,
+ MARKDOWNX_UPLOAD_MAX_SIZE,
)
+
class ImageForm(forms.Form):
+
image = forms.ImageField()
def save(self, commit=True):
- img = scale_and_crop(self.files['image'], **MARKDOWNX_IMAGE_SIZE)
+ img = scale_and_crop(self.files['image'], **MARKDOWNX_IMAGE_MAX_SIZE)
thumb_io = StringIO.StringIO()
img.save(thumb_io, self.files['image'].content_type.split('/')[-1].upper())
-
+
file_name = str(self.files['image'])
img = InMemoryUploadedFile(thumb_io, "image", file_name, self.files['image'].content_type, thumb_io.len, None)
-
+
unique_file_name = self.get_unique_file_name(file_name)
full_path = os.path.join(settings.MEDIA_ROOT, MARKDOWNX_MEDIA_PATH, unique_file_name)
if not os.path.exists(os.path.dirname(full_path)):
os.makedirs(os.path.dirname(full_path))
-
+
destination = open(full_path, 'wb+')
for chunk in img.chunks():
destination.write(chunk)
@@ -47,9 +49,9 @@ class ImageForm(forms.Form):
def clean(self):
upload = self.cleaned_data['image']
content_type = upload.content_type
- if content_type in MARKDOWNX_CONTENT_TYPES:
- if upload._size > MARKDOWNX_MAX_UPLOAD_SIZE:
- raise forms.ValidationError(_('Please keep filesize under %(max)s. Current filesize %(current)s') % {'max':filters.filesizeformat(MARKDOWNX_MAX_UPLOAD_SIZE), 'current':filters.filesizeformat(upload._size)})
+ if content_type in MARKDOWNX_UPLOAD_CONTENT_TYPES:
+ if upload._size > MARKDOWNX_UPLOAD_MAX_SIZE:
+ raise forms.ValidationError(_('Please keep filesize under %(max)s. Current filesize %(current)s') % {'max':filters.filesizeformat(MARKDOWNX_UPLOAD_MAX_SIZE), 'current':filters.filesizeformat(upload._size)})
else:
raise forms.ValidationError(_('File type is not supported'))
diff --git a/markdownx/locale/en/LC_MESSAGES/django.mo b/markdownx/locale/en/LC_MESSAGES/django.mo
index 63e96b4..f087204 100644
Binary files a/markdownx/locale/en/LC_MESSAGES/django.mo and b/markdownx/locale/en/LC_MESSAGES/django.mo differ
diff --git a/markdownx/locale/en/LC_MESSAGES/django.po b/markdownx/locale/en/LC_MESSAGES/django.po
index 77e5404..fe30718 100644
--- a/markdownx/locale/en/LC_MESSAGES/django.po
+++ b/markdownx/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-11-01 10:34+0000\n"
+"POT-Creation-Date: 2015-09-06 07:38+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME
\n"
"Language-Team: LANGUAGE \n"
@@ -17,27 +17,19 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: forms.py:52
+#: forms.py:54
#, python-format
msgid "Please keep filesize under %(max)s. Current filesize %(current)s"
msgstr ""
-#: forms.py:54
+#: forms.py:56
msgid "File type is not supported"
msgstr ""
-#: settings.py:13
+#: settings.py:17
msgid "English"
msgstr ""
-#: settings.py:14
+#: settings.py:18
msgid "Polish"
msgstr ""
-
-#: templates/markdownx/widget.html:5 templates/markdownx/widget.html.py:15
-msgid "Editor"
-msgstr ""
-
-#: templates/markdownx/widget.html:9 templates/markdownx/widget.html.py:17
-msgid "Preview"
-msgstr ""
diff --git a/markdownx/locale/pl/LC_MESSAGES/django.mo b/markdownx/locale/pl/LC_MESSAGES/django.mo
index 60c32c4..3543393 100644
Binary files a/markdownx/locale/pl/LC_MESSAGES/django.mo and b/markdownx/locale/pl/LC_MESSAGES/django.mo differ
diff --git a/markdownx/locale/pl/LC_MESSAGES/django.po b/markdownx/locale/pl/LC_MESSAGES/django.po
index fe1dfb6..524832d 100644
--- a/markdownx/locale/pl/LC_MESSAGES/django.po
+++ b/markdownx/locale/pl/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-11-01 10:34+0000\n"
+"POT-Creation-Date: 2015-09-06 07:38+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -19,27 +19,19 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
-#: forms.py:52
+#: forms.py:54
#, python-format
msgid "Please keep filesize under %(max)s. Current filesize %(current)s"
msgstr "Maksymalny rozmiar pliku wynosi %(max)s. Rozmiar obecnie wgrywanego pliku wynosi %(current)s"
-#: forms.py:54
+#: forms.py:56
msgid "File type is not supported"
msgstr "Typ pliku nie jest obsługiwany"
-#: settings.py:13
+#: settings.py:17
msgid "English"
-msgstr "Angielski"
+msgstr ""
-#: settings.py:14
+#: settings.py:18
msgid "Polish"
-msgstr "Polski"
-
-#: templates/markdownx/widget.html:5 templates/markdownx/widget.html.py:15
-msgid "Editor"
-msgstr "Edytor"
-
-#: templates/markdownx/widget.html:9 templates/markdownx/widget.html.py:17
-msgid "Preview"
-msgstr "Podgląd"
+msgstr ""
diff --git a/markdownx/models.py b/markdownx/models.py
new file mode 100644
index 0000000..201287c
--- /dev/null
+++ b/markdownx/models.py
@@ -0,0 +1,11 @@
+from django.db import models
+
+from .fields import MarkdownxFormField
+
+
+class MarkdownxField(models.TextField):
+
+ def formfield(self, **kwargs):
+ defaults = {'form_class': MarkdownxFormField}
+ defaults.update(kwargs)
+ return super(MarkdownxField, self).formfield(**defaults)
diff --git a/markdownx/settings.py b/markdownx/settings.py
index 5df1283..d2d2e3e 100755
--- a/markdownx/settings.py
+++ b/markdownx/settings.py
@@ -1,18 +1,21 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
-# markdown.markdown kwargs
-MARKDOWNX_MARKDOWN_KWARGS = getattr(settings, 'MARKDOWNX_MARKDOWN_KWARGS', dict())
+# Markdown extensions
+MARKDOWNX_MARKDOWN_EXTENSIONS = getattr(settings, 'MARKDOWNX_MARKDOWN_EXTENSIONS', [])
-# path
+# Media path
MARKDOWNX_MEDIA_PATH = getattr(settings, 'MARKDOWNX_MEDIA_PATH', 'markdownx/')
-# image
-MARKDOWNX_MAX_UPLOAD_SIZE = getattr(settings, 'MARKDOWNX_MAX_UPLOAD_SIZE', 52428800) # 50MB
-MARKDOWNX_CONTENT_TYPES = getattr(settings, 'MARKDOWNX_CONTENT_TYPES', ['image/jpeg', 'image/png'])
-MARKDOWNX_IMAGE_SIZE = getattr(settings, 'MARKDOWNX_IMAGE_SIZE', {'size': (500, 500), 'quality': 90,})
+# Image
+MARKDOWNX_UPLOAD_MAX_SIZE = getattr(settings, 'MARKDOWNX_UPLOAD_MAX_SIZE', 52428800) # 50MB
+MARKDOWNX_UPLOAD_CONTENT_TYPES = getattr(settings, 'MARKDOWNX_UPLOAD_CONTENT_TYPES', ['image/jpeg', 'image/png'])
+MARKDOWNX_IMAGE_MAX_SIZE = getattr(settings, 'MARKDOWNX_IMAGE_MAX_SIZE', {'size': (500, 500), 'quality': 90,})
-# translations
+# Editor
+MARKDOWNX_EDITOR_RESIZABLE = getattr(settings, 'MARKDOWNX_EDITOR_RESIZABLE', False)
+
+# Translations
LANGUAGES = getattr(settings, 'LANGUAGES', (
('en', _('English')),
('pl', _('Polish')),
diff --git a/markdownx/static/js/markdownx.js b/markdownx/static/js/markdownx.js
deleted file mode 100644
index 5ceba07..0000000
--- a/markdownx/static/js/markdownx.js
+++ /dev/null
@@ -1,168 +0,0 @@
-$.fn.extend({
- markdownx: function(options) {
- var defaults = {};
- var opts = $.extend(defaults, options);
-
- var $this = $(this);
- var $markdownx_editor = $this.find('#markdownx_editor');
- var $markdownx_preview = $this.find('#markdownx_preview');
-
- var ms;
- var markdownify = function() {
- clearTimeout(ms);
- ms = setTimeout(getMarkdown, 500);
- };
-
- var getMarkdown = function() {
- form = new FormData();
- form.append("content", $markdownx_editor.val());
- form.append("csrfmiddlewaretoken", getCookie('csrftoken'))
-
- $.ajax({
- type: 'POST',
- url: '/markdownx/markdownify/',
- data: form,
- processData: false,
- contentType: false,
-
- success: function(response) {
- $markdownx_preview.html(response);
- updateHeight();
- },
-
- error: function(response) {
- console.log("error", response);
- },
- });
- }
-
- var updateHeight = function() {
- $markdownx_editor.innerHeight($markdownx_editor.prop('scrollHeight'))
- }
-
- var insertImage = function(image_path) {
- var cursor_pos = $markdownx_editor.prop('selectionStart');
- var text = $markdownx_editor.val();
- var textBeforeCursor = text.substring(0, cursor_pos);
- var textAfterCursor = text.substring(cursor_pos, text.length);
- var textToInsert = "";
-
- $markdownx_editor.val(textBeforeCursor + textToInsert + textAfterCursor);
- $markdownx_editor.prop('selectionStart', cursor_pos + textToInsert.length);
- $markdownx_editor.prop('selectionEnd', cursor_pos + textToInsert.length);
- $markdownx_editor.keyup();
-
- updateHeight();
- markdownify();
- }
-
- var getCookie = function(name) {
- var cookieValue = null;
- if (document.cookie && document.cookie != '') {
- var cookies = document.cookie.split(';');
- for (var i = 0; i < cookies.length; i++) {
- var cookie = jQuery.trim(cookies[i]);
- if (cookie.substring(0, name.length + 1) == (name + '=')) {
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
- break;
- }
- }
- }
- return cookieValue;
- }
-
- var sendFile = function(file) {
- form = new FormData();
- form.append("image", file);
- form.append("csrfmiddlewaretoken", getCookie('csrftoken'))
-
- $.ajax({
- type: 'POST',
- url: '/markdownx/upload/',
- data: form,
- processData: false,
- contentType: false,
-
- beforeSend: function() {
- console.log("uploading...");
- $markdownx_editor.fadeTo("fast", 0.3);
- },
-
- success: function(response) {
- $markdownx_editor.fadeTo("fast", 1);
- if (response['image_path']) {
- insertImage(response['image_path']);
- console.log("success", response);
- } else
- console.log('error: wrong response', response);
- },
-
- error: function(response) {
- console.log("error", response);
- $markdownx_editor.fadeTo("fast", 1 );
- },
- });
- }
-
- updateHeight();
- markdownify();
-
- $markdownx_editor.on('keydown', function(e) {
- if (e.keyCode === 9) { // Tab
- var start = this.selectionStart;
- var end = this.selectionEnd;
-
- var $this = $(this);
- var value = $this.val();
-
- $this.val(value.substring(0, start) + "\t" + value.substring(end));
- this.selectionStart = this.selectionEnd = start + 1;
-
- markdownify();
-
- return false;
- }
- });
-
- // On text change
- $markdownx_editor.on('input propertychange', function() {
- updateHeight();
- markdownify();
- });
-
- // Upload functionality
- $('html').on('dragenter dragover drop dragleave', function(e) {
- e.preventDefault();
- e.stopPropagation();
- });
-
- $markdownx_editor.on('dragenter dragover', function(e) {
- e.originalEvent.dataTransfer.dropEffect= 'copy';
-
- e.preventDefault();
- e.stopPropagation();
- });
-
- $markdownx_editor.on('dragleave', function(e) {
- e.preventDefault();
- e.stopPropagation();
- });
-
- $markdownx_editor.on('drop', function(e) {
- if (e.originalEvent.dataTransfer){
- if (e.originalEvent.dataTransfer.files.length) {
- for (var i = 0; i < e.originalEvent.dataTransfer.files.length; i++) {
- sendFile(e.originalEvent.dataTransfer.files[i]);
- }
- }
- }
-
- e.preventDefault();
- e.stopPropagation();
- });
- }
-});
-
-$(document).ready(function() {
- $('#markdownx').markdownx();
-});
diff --git a/markdownx/static/markdownx/admin/css/markdownx.css b/markdownx/static/markdownx/admin/css/markdownx.css
new file mode 100644
index 0000000..e6e29b4
--- /dev/null
+++ b/markdownx/static/markdownx/admin/css/markdownx.css
@@ -0,0 +1,124 @@
+.markdownx {
+ display: inline-block;
+}
+.markdownx .markdownx-editor,
+.markdownx .markdownx-preview {
+ margin-left: 0;
+ width: 610px;
+}
+.markdownx .markdownx-preview {
+ overflow-y: scroll;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ font-size: 100%;
+ font-size: 1em;
+ line-height: 1.5em;
+}
+.markdownx .markdownx-preview * {
+ line-height: 1.5;
+}
+
+
+/* Django admin overrides */
+
+.markdownx .markdownx-preview a { color: #5b80b2; text-decoration:underline; }
+.markdownx .markdownx-preview a:visited { color: #0b0080; }
+.markdownx .markdownx-preview a:hover { color: #0645ad; }
+.markdownx .markdownx-preview a:active { color:#faa700; }
+.markdownx .markdownx-preview a:focus { outline: thin dotted; }
+.markdownx .markdownx-preview a:hover, .markdownx .markdownx-preview a:active { outline: 0; }
+.markdownx .markdownx-preview p {
+ margin: 1em 0;
+ padding: 0;
+ font-size: 14px;
+}
+.markdownx .markdownx-preview img { max-width:100%; }
+.markdownx .markdownx-preview h1,
+.markdownx .markdownx-preview h2,
+.markdownx .markdownx-preview h3,
+.markdownx .markdownx-preview h4,
+.markdownx .markdownx-preview h5,
+.markdownx .markdownx-preview h6 {
+ font-weight: normal;
+ color: #111;
+ margin-top: 0.75em;
+ margin-bottom: 0.75em;
+ padding: 0;
+ background: none;
+}
+.markdownx .markdownx-preview h4,
+.markdownx .markdownx-preview h5,
+.markdownx .markdownx-preview h6 { font-weight: bold; }
+.markdownx .markdownx-preview h1 { font-size: 2.5em; }
+.markdownx .markdownx-preview h2 { font-size: 2em; }
+.markdownx .markdownx-preview h3 { font-size: 1.5em; }
+.markdownx .markdownx-preview h4 { font-size: 1.2em; }
+.markdownx .markdownx-preview h5 { font-size: 1em; }
+.markdownx .markdownx-preview h6 { font-size: 0.9em; }
+
+.markdownx .markdownx-preview blockquote {
+ color: #666666;
+ margin: 0;
+ padding-left: 1.5em;
+ border-left: 0.5em #eee solid;
+}
+.markdownx .markdownx-preview hr {
+ display: block;
+ height: 0px;
+ border: 0;
+ font-style: italic;
+ border-bottom: 1px solid #ccc;
+ margin: 20px 0;
+ padding: 0;
+}
+.markdownx .markdownx-preview pre,
+.markdownx .markdownx-preview code,
+.markdownx .markdownx-preview kbd,
+.markdownx .markdownx-preview samp {
+ font-family: monospace, monospace;
+ font-size: 14px;
+}
+.markdownx .markdownx-preview code,
+.markdownx .markdownx-preview pre {
+ margin: 0 2px;
+ padding: 0px 5px;
+ border: 1px solid #ddd;
+ background-color: #f8f8f8;
+ border-radius: 2px;
+ color: #444;
+}
+.markdownx .markdownx-preview pre {
+ margin: 1.5em 0 1.5em 0;
+ padding: 1em;
+ white-space: pre;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+.markdownx .markdownx-preview pre code {
+ margin: 0;
+ padding: 0;
+ background: transparent;
+ border: none;
+}
+.markdownx .markdownx-preview b, .markdownx .markdownx-preview strong { font-weight: bold; }
+.markdownx .markdownx-preview dfn { font-style: italic; }
+.markdownx .markdownx-preview ins { background: #ff9; color: #000; text-decoration: none; }
+.markdownx .markdownx-preview mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
+.markdownx .markdownx-preview sub, .markdownx .markdownx-preview sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
+.markdownx .markdownx-preview sup { top: -0.5em; }
+.markdownx .markdownx-preview sub { bottom: -0.25em; }
+.markdownx .markdownx-preview ul,
+.markdownx .markdownx-preview ol {
+ margin: 1em 0 !important; padding: 0 0 0 2em !important;
+}
+.markdownx .markdownx-preview ul li,
+.markdownx .markdownx-preview ol li {
+ font-size: 14px !important;
+ margin-bottom: 0.75em;
+}
+.markdownx .markdownx-preview li p:last-child { margin:0 }
+.markdownx .markdownx-preview dd { margin: 0 0 0 2em; }
+.markdownx .markdownx-preview img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
+.markdownx .markdownx-preview table { border-collapse: collapse; border-spacing: 0; }
+.markdownx .markdownx-preview th { background: none; background: #f8f8f8; font-size: 14px; }
+.markdownx .markdownx-preview td { vertical-align: top; font-size: 14px; }
diff --git a/markdownx/static/markdownx/js/markdownx.js b/markdownx/static/markdownx/js/markdownx.js
new file mode 100644
index 0000000..6969117
--- /dev/null
+++ b/markdownx/static/markdownx/js/markdownx.js
@@ -0,0 +1,179 @@
+(function ($) {
+ if (!$) {
+ $ = django.jQuery
+ }
+ $.fn.markdownx = function() {
+
+ return this.each( function() {
+
+ var getMarkdown = function() {
+ form = new FormData();
+ form.append("content", markdownxEditor.val());
+ form.append("csrfmiddlewaretoken", getCookie('csrftoken'))
+
+ $.ajax({
+ type: 'POST',
+ url: '/markdownx/markdownify/',
+ data: form,
+ processData: false,
+ contentType: false,
+
+ success: function(response) {
+ markdownxPreview.html(response);
+ updateHeight();
+ },
+
+ error: function(response) {
+ console.log("error", response);
+ },
+ });
+ }
+
+ var updateHeight = function() {
+ if (isMarkdownxEditorResizable) {
+ markdownxEditor.innerHeight(markdownxEditor.prop('scrollHeight'));
+ }
+ }
+
+ var insertImage = function(image_path) {
+ var cursor_pos = markdownxEditor.prop('selectionStart');
+ var text = markdownxEditor.val();
+ var textBeforeCursor = text.substring(0, cursor_pos);
+ var textAfterCursor = text.substring(cursor_pos, text.length);
+ var textToInsert = "";
+
+ markdownxEditor.val(textBeforeCursor + textToInsert + textAfterCursor);
+ markdownxEditor.prop('selectionStart', cursor_pos + textToInsert.length);
+ markdownxEditor.prop('selectionEnd', cursor_pos + textToInsert.length);
+ markdownxEditor.keyup();
+
+ updateHeight();
+ markdownify();
+ }
+
+ var getCookie = function(name) {
+ var cookieValue = null;
+ if (document.cookie && document.cookie != '') {
+ var cookies = document.cookie.split(';');
+ for (var i = 0; i < cookies.length; i++) {
+ var cookie = $.trim(cookies[i]);
+ if (cookie.substring(0, name.length + 1) == (name + '=')) {
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+ break;
+ }
+ }
+ }
+ return cookieValue;
+ }
+
+ var sendFile = function(file) {
+ form = new FormData();
+ form.append("image", file);
+ form.append("csrfmiddlewaretoken", getCookie('csrftoken'))
+
+ $.ajax({
+ type: 'POST',
+ url: '/markdownx/upload/',
+ data: form,
+ processData: false,
+ contentType: false,
+
+ beforeSend: function() {
+ console.log("uploading...");
+ markdownxEditor.fadeTo("fast", 0.3);
+ },
+
+ success: function(response) {
+ markdownxEditor.fadeTo("fast", 1);
+ if (response['image_path']) {
+ insertImage(response['image_path']);
+ console.log("success", response);
+ } else
+ console.log('error: wrong response', response);
+ },
+
+ error: function(response) {
+ console.log("error", response);
+ markdownxEditor.fadeTo("fast", 1 );
+ },
+ });
+ }
+
+ var timeout;
+ var markdownify = function() {
+ clearTimeout(timeout);
+ timeout = setTimeout(getMarkdown, 500);
+ };
+
+ // Events
+
+ var onKeyDownEvent = function(e) {
+ if (e.keyCode === 9) { // Tab
+ var start = this.selectionStart;
+ var end = this.selectionEnd;
+
+ $(this).val($(this).val().substring(0, start) + "\t" + $(this).val().substring(end));
+ this.selectionStart = this.selectionEnd = start + 1;
+
+ markdownify();
+
+ return false;
+ }
+ }
+
+ var onInputChangeEvent = function() {
+ updateHeight();
+ markdownify();
+ }
+
+ var onHtmlEvents = function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ var onDragEnterEvent = function(e) {
+ e.originalEvent.dataTransfer.dropEffect= 'copy';
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ var onDragLeaveEvent = function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ var onDropEvent = function(e) {
+ if (e.originalEvent.dataTransfer){
+ if (e.originalEvent.dataTransfer.files.length) {
+ for (var i = 0; i < e.originalEvent.dataTransfer.files.length; i++) {
+ sendFile(e.originalEvent.dataTransfer.files[i]);
+ }
+ }
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ // Init
+
+ var markdownxEditor = $(this).find('.markdownx-editor');
+ var markdownxPreview = $(this).find('.markdownx-preview');
+
+ var isMarkdownxEditorResizable = markdownxEditor.is("[data-markdownx-editor-resizable]");
+
+ $('html').on('dragenter.markdownx dragover.markdownx drop.markdownx dragleave.markdownx', onHtmlEvents);
+ markdownxEditor.on('keydown.markdownx', onKeyDownEvent);
+ markdownxEditor.on('input.markdownx propertychange.markdownx', onInputChangeEvent);
+ markdownxEditor.on('dragenter.markdownx dragover.markdownx', onDragEnterEvent);
+ markdownxEditor.on('dragleave.markdownx', onDragLeaveEvent);
+ markdownxEditor.on('drop.markdownx', onDropEvent);
+
+ updateHeight();
+ markdownify();
+ });
+ };
+
+ $(function() {
+ $('.markdownx').markdownx();
+ });
+})(jQuery);
diff --git a/markdownx/templates/markdownx/widget.html b/markdownx/templates/markdownx/widget.html
index 8d1c21f..b795aac 100755
--- a/markdownx/templates/markdownx/widget.html
+++ b/markdownx/templates/markdownx/widget.html
@@ -1,7 +1,4 @@
-{% load i18n %}
-
-
{% trans "Editor" %}
+
{{ markdownx_editor }}
-
{% trans "Preview" %}
-
+
diff --git a/markdownx/views.py b/markdownx/views.py
index dd38911..e2d8421 100755
--- a/markdownx/views.py
+++ b/markdownx/views.py
@@ -1,21 +1,22 @@
+import markdown
+
from django.views.generic.edit import View, FormView
from django.http import HttpResponse, JsonResponse
-import markdown
-
-from . import forms
-from .settings import MARKDOWNX_MARKDOWN_KWARGS
+from .forms import ImageForm
+from .settings import MARKDOWNX_MARKDOWN_EXTENSIONS
class MarkdownifyView(View):
def post(self, request, *args, **kwargs):
- return HttpResponse(markdown.markdown(request.POST['content'], **MARKDOWNX_MARKDOWN_KWARGS))
+ return HttpResponse(markdown.markdown(request.POST['content'], extensions=MARKDOWNX_MARKDOWN_EXTENSIONS))
class ImageUploadView(FormView):
+
template_name = "dummy.html"
- form_class = forms.ImageForm
+ form_class = ImageForm
success_url = '/'
def form_invalid(self, form):
@@ -24,7 +25,7 @@ class ImageUploadView(FormView):
return JsonResponse(form.errors, status=400)
else:
return response
-
+
def form_valid(self, form):
image_path = form.save()
response = super(ImageUploadView, self).form_valid(form)
diff --git a/markdownx/widgets.py b/markdownx/widgets.py
index 1a77e79..a462d80 100755
--- a/markdownx/widgets.py
+++ b/markdownx/widgets.py
@@ -1,31 +1,33 @@
-from django.conf import settings
-from django.forms import Textarea
+from django import forms
from django.template import Context
from django.template.loader import get_template
+from django.contrib.admin import widgets
-class MarkdownxInput(Textarea):
- def __init__(self, attrs=None):
-
- default_attrs = {
- 'id': 'markdownx_editor',
- }
- if attrs:
- default_attrs.update(attrs)
-
- super(Textarea, self).__init__(default_attrs)
+class MarkdownxWidget(forms.Textarea):
def render(self, name, value, attrs=None):
- textarea = Textarea.render(self, name, value)
+ widget = super(MarkdownxWidget, self).render(name, value, attrs)
t = get_template('markdownx/widget.html')
c = Context({
- 'markdownx_editor': textarea,
+ 'markdownx_editor': widget,
})
return t.render(c)
class Media:
js = (
- 'js/markdownx.js',
+ 'markdownx/js/markdownx.js',
+ )
+
+
+class AdminMarkdownxWidget(MarkdownxWidget, widgets.AdminTextareaWidget):
+
+ class Media:
+ css = {
+ 'all': ('markdownx/admin/css/markdownx.css',)
+ }
+ js = (
+ 'markdownx/js/markdownx.js',
)