Commits

mitar  committed d0fad35

Added support for markups which support adding text plugins to the content.

  • Participants
  • Parent commits 2d88cc5

Comments (0)

Files changed (14)

File cmsplugin_markup/cms_plugins.py

 from cms.plugin_base import CMSPluginBase
 from cms.plugin_pool import plugin_pool
 
+from cmsplugin_markup import utils
 from cmsplugin_markup.models import MarkupField
 from cmsplugin_markup.forms import MarkupForm
 
             })
         return context
 
+    def save_model(self, request, obj, form, change):
+        obj.clean_plugins()
+        super(MarkupPlugin, self).save_model(request, obj, form, change)
+
+    def change_view(self, request, object_id, extra_context={}):
+        extra_context.update({
+            'text_plugins': plugin_pool.get_text_enabled_plugins(self.placeholder, self.page),
+            'name': 'markupeditor',
+            'used_plugins': pluginmodel.CMSPlugin.objects.filter(parent=object_id),
+            'markup_plugins': [c() for c in utils.get_list_of_markup_classes().values()],
+            })
+        return super(MarkupPlugin, self).change_view(request, object_id, extra_context=extra_context)
+
+    def add_view(self, request, form_url='', extra_context={}):
+        extra_context.update({
+            'text_plugins': plugin_pool.get_text_enabled_plugins(self.placeholder, self.page),
+            'name': 'markupeditor',
+            'markup_plugins': [c() for c in utils.get_list_of_markup_classes().values()],
+            })
+        return super(MarkupPlugin, self).add_view(request, form_url, extra_context=extra_context);
+
     def get_plugin_urls(self):
         from django.conf.urls.defaults import patterns, url
 
         if not shortcuts.get_object_or_404(pluginmodel.CMSPlugin, pk=request.POST.get('plugin_id')).placeholder.has_change_permission(request):
             raise http.Http404
         
+        if not request.POST.get('markup'):
+            return http.HttpResponse('')
+
         return http.HttpResponse(markup.markup_parser(request.POST.get('text'), request.POST.get('markup')))
 
 plugin_pool.register_plugin(MarkupPlugin)

File cmsplugin_markup/models.py

             default=MARKUP_CHOICES[0][0] if len(MARKUP_CHOICES) == 1 else models.NOT_PROVIDED,
             )
 
+    search_fields = ('body_html',)
+
     def __unicode__(self):
         return u'%s' %(truncate_words(strip_tags(self.body_html), 3)[:30]+'...')
 
     def save(self, *args, **kwargs):
         self.body_html = utils.markup_parser(self.body, self.markup)
         return super(MarkupField, self).save(*args, **kwargs)
+
+    def clean_plugins(self):
+        ids = utils.plugin_id_list(self.body, self.markup)
+        plugins = CMSPlugin.objects.filter(parent=self)
+        for plugin in plugins:
+            if not str(plugin.pk) in ids:
+                plugin.delete() #delete plugins that are not referenced in the markup anymore
+
+    def post_copy(self, old_instance, ziplist):
+        """
+        Fix references to plugins
+        """
+
+        replace_ids = {}
+        for new, old in ziplist:
+            replace_ids[old.pk] = new.pk
+
+        self.body = utils.replace_plugins(old_instance.markupfield.body, replace_ids, self.markup)
+        self.save()

File cmsplugin_markup/plugins/__init__.py

+from cmsplugin_markup.plugins.base import *

File cmsplugin_markup/plugins/base.py

+class MarkupBase(object):
+    text_enabled_plugins = False
+
+    def plugin_id_list(self, text):
+        """
+        Returns the list of plugins inserted and currently used in the markup text.
+        """
+        return []
+
+    def replace_plugins(self, text, id_dict):
+        """
+        Replaces references to plugins in the markup text with new ids.
+        """
+        return text
+
+    def plugin_markup(self):
+        """
+        Returns JavaScript code for anonymous function which construct plugin markup given plugin_id, icon_src and icon_alt arguments. It should be marked as safe to prevent escaping.
+        """
+        return None
+
+    def plugin_regexp(self):
+        """
+        Returns JavaScript code for anonymous function which construct plugin regexp given plugin_id. It should be marked as safe to prevent escaping.
+        """
+        return None

File cmsplugin_markup/plugins/markdown/mark_down.py

 from django.conf import settings
 from django.utils.encoding import smart_str, force_unicode
 
-class Markup(object):
+from cmsplugin_markup.plugins import MarkupBase
+
+class Markup(MarkupBase):
 
     name = 'Markdown'
     identifier = 'markdown'

File cmsplugin_markup/plugins/restructuredtext/__init__.py

 from django.conf import settings
 from django.utils.encoding import smart_str, force_unicode
 
-class Markup(object):
+from cmsplugin_markup.plugins import MarkupBase
+
+class Markup(MarkupBase):
 
     name = 'ReST (ReStructured Text)'
     identifier = 'restructuredtext'

File cmsplugin_markup/plugins/textile/__init__.py

 from django.conf import settings
 from django.utils.encoding import smart_str, force_unicode
 
-class Markup(object):
+from cmsplugin_markup.plugins import MarkupBase
+
+class Markup(MarkupBase):
 
     name = 'Textile'
     identifier = 'textile'

File cmsplugin_markup/static/cmsplugin_markup/markup.css

 	height: 5px;
 	overflow: hidden;
 }
+
+#plugins_list .divimg {
+	width: 32px;
+	height: 32px;
+	float: left;
+	margin-right: 5px;
+	text-align: center;
+}
+
+#plugins_list .divimg img {
+	max-width: 32px;
+	max-height: 32px;
+	cursor: pointer;
+}
+
+#plugins_list span {
+	cursor: pointer;
+}
+
+#plugins_list ul {
+	padding: 0;
+}
+
+#plugins_list li {
+	min-height: 32px;
+	list-style-image: none;
+	list-style-type: none;
+	margin: 0;
+}
+
+#plugins_list li:hover {
+	background-color: #FFFFE0;
+}
+
+.iftextplugins {
+	display: none;
+}

File cmsplugin_markup/static/cmsplugin_markup/markup.js

 // Automatic preview through XHR
 // Based on Trac version, http://trac.edgewall.org/
 
+var forceAutoPreview = false;
+
 (function($) {
   // Enable automatic previewing to <textarea> elements.
   //
       var updating = false;
       var textarea = this;
       var data = {};
-      for (var key in args)
-        data[key] = args[key];
+      for (var key in args) {
+        if (typeof(args[key]) !== "function") {
+          data[key] = args[key];
+        }
+      }
       data["text"] = textarea.value;
       
       // Request a preview through XHR
       function request() {
         var text = textarea.value;
-        if (!updating && (text != data["text"])) {
+        if (!updating && ((text != data["text"]) || forceAutoPreview)) {
           updating = true;
+          forceAutoPreview = false
           data["text"] = text;
+          for (var key in args) {
+            if (typeof(args[key]) === "function") {
+              data[key] = args[key]();
+            }
+          }
+
           $.ajax({
             type: "POST", url: href, data: data, dataType: "html",
             success: function(data) {
   }
 })(jQuery);
 
-(function ($){
-  $(document).ready(function(){
-      $.fn.cmsPatchCSRF();
-  });
-})(jQuery);
+jQuery(document).ready(function($) {
+  $.fn.cmsPatchCSRF();
+});
 
 jQuery(document).ready(function($) {
   // Only if preview exists
   $('#plugin-preview').each(function() {
     var preview = $(this);
     $('#id_body').autoPreview(auto_preview_url, {
-        'markup': $('#id_markup').val(),
+        'markup': function () {return $('#id_markup').val()},
         'plugin_id': plugin_id,
         'page_id': page_id
       },
       function (textarea, text, data) {
         preview.html(data);
-        parent.setiframeheight($('body').height() + 20, 11);
+        if (parent.setiframeheight) {
+          parent.setiframeheight($('body').height() + 20, 11);
+        }
       });
   });
 });
     }
     
     var grip = $('<div class="django-grip"/>').mousedown(beginDrag)[0];
-    textarea.wrap('<div class="django-resizable"><div></div></div>')
-            .parent().append(grip);
+    textarea.wrap('<div class="django-resizable"><div></div></div>').parent().append(grip);
     grip.style.marginLeft = (this.offsetLeft - grip.offsetLeft) + 'px';
     grip.style.marginRight = (grip.offsetWidth - this.offsetWidth) +'px';
   });
 });
+
+function MarkupEditorPlaceholderBridge(field) {
+  this.field = field;
+}
+
+MarkupEditorPlaceholderBridge.prototype.insertText = function(text) {
+  var selectionStart = this.field.get(0).selectionStart;
+  var selectionEnd = this.field.get(0).selectionEnd;
+  this.field.val(this.field.val().substring(0, selectionStart) + text + this.field.val().substring(selectionEnd));
+};
+
+MarkupEditorPlaceholderBridge.prototype.replaceContent = function(old, rep) {
+  this.field.val(this.field.val().replace(old, rep));
+};
+
+MarkupEditorPlaceholderBridge.prototype.selectedObject = function() {
+  return null;
+};
+
+function add_plugin_to_list(plugin_id, icon_src, icon_alt) {
+  (function ($) {
+    var img = $('<img/>').attr({
+     'src': icon_src,
+     'alt': icon_alt,
+     'title': icon_alt
+    }).click(edit_plugin_click);
+    var divimg = $('<div/>').addClass('divimg').append(img);
+    var span = $('<span/>').html(icon_alt + '<br />Plugin ID: ' + plugin_id).click(edit_plugin_click);
+    var desc = $('<div/>').append(span);
+    var li = $('<li/>').attr({
+      'id': 'plugin_obj_' + plugin_id
+    }).append(divimg).append(desc);
+    li.appendTo('#plugins_list ul');
+  })(jQuery);
+}
+
+function add_plugin_for_markup(type, parent_id, language, name) {
+  (function ($) {
+    $.post("add-plugin/", {
+      parent_id: parent_id,
+      plugin_type: type
+    }, function(data) {
+      if ('error' != data) {
+        var plugin_id = data;
+        edit_plugin_for_markup(plugin_id, name);
+        editPluginPopupCallbacks[data] = function(plugin_id, icon_src, icon_alt) {
+          texteditor = get_editor(name);
+          texteditor.insertText(plugin_admin_markup(plugin_id, icon_src, icon_alt));
+          add_plugin_to_list(plugin_id, icon_src, icon_alt);
+          forceAutoPreview = true;
+          $('#id_body').blur();
+          editPluginPopupCallbacks[data] = null;
+        };
+      }
+    }, "html");
+  })(jQuery);
+}
+
+function edit_plugin_for_markup(obj_id, name) {
+  (function ($) {
+    editPluginPopupCallbacks[obj_id] = function(plugin_id, icon_src, icon_alt) {
+      var texteditor = get_editor(name);
+      var rExp = plugin_admin_regex(obj_id);
+      try {
+        texteditor.replaceContent(rExp, plugin_admin_markup(plugin_id, icon_src, icon_alt));
+      } catch (e) {}
+      $('#plugin_obj_' + plugin_id + ' img').attr({
+        'src': icon_src,
+        'alt': icon_alt,
+        'title': icon_alt
+      });
+      $('#plugin_obj_' + plugin_id + ' span').html(icon_alt + '<br />Plugin ID: ' + plugin_id);
+      forceAutoPreview = true;
+      $('#id_body').blur();
+      editPluginPopupCallbacks[obj_id] = null;
+    };
+    
+    window.open("edit-plugin/" + obj_id + "/?_popup=1",
+                "Edit_plugin_object",
+                "menubar=no,titlebar=no,toolbar=no,resizable=yes"
+                + ",width=800,height=300,top=0,left=0,scrollbars=yes"
+                + ",location=no"
+               );
+  })(jQuery);
+}
+
+function plugin_admin_regex(plugin_id) {
+  return markupPlugins[$('#id_markup').val()]['regex'](plugin_id);
+}
+
+function plugin_admin_markup(plugin_id, icon_src, icon_alt) {
+  return markupPlugins[$('#id_markup').val()]['markup'](plugin_id, icon_src, icon_alt);
+}
+
+function showhideplugins() {
+  (function ($) {
+    var plugin = markupPlugins[$('#id_markup').val()];
+    window.console.log($('#id_markup').val());
+    window.console.log(markupPlugins);
+    if ((typeof(plugin) !== "undefined") && plugin['textenabled']) {
+      $('.iftextplugins').show();
+    }
+    else {
+      $('.iftextplugins').hide();
+    }
+  })(jQuery);
+}
+
+jQuery(document).ready(function($) {
+  $('#id_markup').change(function() {
+    forceAutoPreview = true;
+    $('#id_body').blur();
+    showhideplugins();
+  });
+  showhideplugins();
+});

File cmsplugin_markup/templates/cmsplugin_markup/markup_plugin_change_form.html

 {% extends "admin/cms/page/plugin_change_form.html" %}
 
+{% load i18n %}
+
 {% block top %}
 {% if preview %}
 <div id="plugin-preview">
 {% endif %}
 {% endblock %}
 
+{% block fieldsets %}
+<div class="iftextplugins">
+<select name="plugins">
+<option value="" selected="selected">{% filter escapejs %}{% trans "Available Plugins" %}{% endfilter %}</option>{% for p in text_plugins %}
+<option value="{{ p.value }}">{{ p.name }}</option>{% endfor %}
+</select>
+<span class="insert-object addlink">{% filter escapejs %}{% trans "Insert plugin" %}{% endfilter %}</span>
+</div>
+{{ block.super }}
+{% endblock %}
+
+{% block after_field_sets %}{{ block.super }}
+<div class="iftextplugins">
+<p class="help">{% trans "Note: Unused inserted plugins will be automatically removed when saving." %}</p>
+<div id="plugins_list">
+<ul></ul>
+</div>
+</div>
+{% endblock %}
+
 {% block extrahead %}{{ block.super }}
 <script type="text/javascript" src="{{ CMS_MEDIA_URL }}js/csrf.js"></script>
 <script type="text/javascript">
 /* ]]> */
 </script>
 <script type="text/javascript" src="{{ STATIC_URL|default:MEDIA_URL }}cmsplugin_markup/markup.js"></script>
+<script type="text/javascript" src="{{ CMS_MEDIA_URL }}js/lib/ui.core.js"></script>
+<script type="text/javascript" src="{{ CMS_MEDIA_URL }}js/placeholder_editor_registry.js"></script>
+<script type="text/javascript">
+/* <![CDATA[ */
+
+var editPluginPopupCallbacks = {};
+var markupPlugins = {};
+
+{% include "cms/plugins/widgets/widget_lib.js" %}
+
+function edit_plugin_click() {
+  var obj = this;
+  (function ($) {
+    var texteditor = get_editor('{{ name|escapejs }}');
+    if (texteditor == null || texteditor.selectedObject == null) {
+      alert("{% filter escapejs %}{% trans "Text editor does not support editing objects." %}{% endfilter %}");
+      return;
+    }
+    var objid = $(obj).closest('li').get(0);
+    if (objid.id == null || objid.id.indexOf("plugin_obj_") != 0) {
+      alert("{% filter escapejs %}{% trans "Not a plugin object" %}{% endfilter %}");
+      return;
+    }
+    var plugin_id = objid.id.substr("plugin_obj_".length);
+    edit_plugin_for_markup(plugin_id, '{{ name|escapejs }}');
+  })(jQuery);
+}
+
+jQuery(document).ready(function($) {
+  var c = new MarkupEditorPlaceholderBridge($('#id_body'));
+  PlaceholderEditorRegistry.registerEditor('{{ name|escapejs }}', c);
+
+  $('span.insert-object').click(function() {
+    var select = $(this).parent().children("select");
+    var pluginvalue = select.attr('value');
+    var splits = window.location.href.split("?")[0].split("/");
+    var parent_id = Number(splits[splits.length - 2]);
+    var language = $('#id_language').attr('value');
+    
+    if (pluginvalue == "") {
+      alert("{% filter escapejs %}{% trans "Please select a plugin type." %}{% endfilter %}");
+      return;
+    }
+    
+    var texteditor = get_editor('{{ name|escapejs }}');
+    if (texteditor == null || texteditor.insertText == null) {
+      alert("{% filter escapejs %}{% trans "Markup does not support inserting objects." %}{% endfilter %}");
+      return;
+    }
+    
+    add_plugin_for_markup(pluginvalue, parent_id, language, '{{ name|escapejs }}')
+  }).css("cursor", "pointer").css("margin", "5px");
+
+{% for p in used_plugins %}
+  add_plugin_to_list('{{ p.pk|escapejs }}', '{{ p.get_instance_icon_src|escapejs }}', '{{ p.get_instance_icon_alt|escapejs }}');
+{% endfor %}
+});
+
+{% for p in markup_plugins %}
+markupPlugins['{{ p.identifier|escapejs }}'] = {
+  'textenabled': {{ p.text_enabled_plugins|yesno:"true,false" }},
+  'markup': {{ p.plugin_markup|default:"null" }},
+  'regex': {{ p.plugin_regexp|default:"null" }}
+};
+{% endfor %}
+/* ]]> */
+</script>
 <link rel="stylesheet" type="text/css" href="{{ STATIC_URL|default:MEDIA_URL }}cmsplugin_markup/markup.css" />
+<link rel="stylesheet" type="text/css" href="{{ CMS_MEDIA_URL }}css/jquery/cupertino/jquery-ui.css" />
 {% endblock %}

File cmsplugin_markup/utils/__init__.py

-from cmsplugin_markup.utils.markup import compile_markup_choices
-from cmsplugin_markup.utils.markup import get_list_of_markup_objects
-from cmsplugin_markup.utils.markup import markup_parser
+from cmsplugin_markup.utils.markup import *
+from cmsplugin_markup.utils.plugins import *

File cmsplugin_markup/utils/markup.py

+from django.conf import settings
 
-def get_list_of_markup_objects(markup_options):
+from cmsplugin_markup.plugins import MarkupBase
+
+def get_list_of_markup_classes(markup_options=settings.CMS_MARKUP_OPTIONS):
     """
     Takes a tuple of python packages that impliment the cmsplugin_markup
     api and return a dict of the objects with identifier as key.
                 continue
             if not hasattr(module.Markup, 'parse'):
                 continue
+            if not issubclass(module.Markup, MarkupBase):
+                continue
 
             objects[module.Markup.identifier] = module.Markup
         except AttributeError:
     """
 
     choices = []
-    objects = get_list_of_markup_objects(markup_options)
+    objects = get_list_of_markup_classes(markup_options)
 
     for identifier, markup_object in objects.iteritems():
         choices.append((identifier, markup_object.name))
 
     return tuple(choices)
 
+def get_markup_object(markup_id):
+    """
+    Returns an markup object based on its id.
+    """
+
+    markup_classes = get_list_of_markup_classes(settings.CMS_MARKUP_OPTIONS)
+    return markup_classes[markup_id]()
+
 def markup_parser(value, parser_identifier):
     """
     Takes a string and a parser identifier and returns a string parsed
     by that parser. If anything goes wrong it returns the original string
     """
-    from django.conf import settings
 
-    markup_objects = get_list_of_markup_objects(settings.CMS_MARKUP_OPTIONS)
-    obj = markup_objects[parser_identifier]()
-
-    return obj.parse(value)
+    return get_markup_object(parser_identifier).parse(value)

File cmsplugin_markup/utils/plugins.py

+from cmsplugin_markup.utils.markup import get_markup_object
+
+def plugin_id_list(text, markup_id):
+    return get_markup_object(markup_id).plugin_id_list(text)
+
+def replace_plugins(text, id_dict, markup_id):
+    return get_markup_object(markup_id).replace_plugins(text, id_dict)

File docs/backends.rst

 
 Specifiying available backends
 ------------------------------
-To determine which MarkUp options to give to the user, cmsplugin-markup looks for a setting called CMS_MARKUP_OPTIONS. It expects this to be a tuple in this format::
+To determine which Markup options to give to the user, cmsplugin-markup looks for a setting called CMS_MARKUP_OPTIONS. It expects this to be a tuple in this format::
 
     CMS_MARKUP_OPTIONS = (
         'cmsplugin_markup.plugins.markdown',
 Backend API
 -----------
 
-To be used as a MarkUp backend, a python package must be laid out in a specific fashion and contain a class that must implement the following methods and variables. This class must be named Markup. An example class is below.::
+To be used as a Markup backend, a python package must be laid out in a specific fashion and contain a class that must implement the following methods and variables. This class must be named Markup. An example class is below.::
 
-    class Markup(object):
+    from cmsplugin_markup.plugins import MarkupBase
+
+    class Markup(MarkupBase):
         name = 'Human Readable Name for the Mark Up'
         identifier = 'Internal Identifier for Mark Up'
 
 
 The ``name`` variable is a human readable name and may be any length. This is the name that will be presented to the user as the option. 
 
-The ``identifier`` variable is stored as a CharField and anything that is allowed in a CharField is allowed in this. It must be unique across all the installed MarkUp Parsers and may be at most 20 characters long. 
+The ``identifier`` variable is stored as a CharField and anything that is allowed in a CharField is allowed in this. It must be unique across all the installed Markup Parsers and may be at most 20 characters long.
 
 The ``parse`` function must accept self, and a value argument. This function is where you will impliment the actual parsing of the user's input. At this point in time this function should fail silently and simply return an unchanged string. This might change in the future.
 
+There are some additional methods and a variable if markup supports adding plugins. In this case ``text_enabled_plugins`` variable should be set to ``True`` and the following methods should be defined.
+
+``plugin_id_list(self, text)`` should return the list of plugins inserted and currently used in the markup text.
+
+``replace_plugins(self, text, id_dict)`` should replace references to plugins in the markup text with new ids.
+
+``plugin_markup(self)`` should return JavaScript code for anonymous function which construct plugin markup given ``plugin_id``, ``icon_src`` and ``icon_alt`` arguments. It should be marked as safe to prevent escaping.
+
+``plugin_regexp(self)`` should return JavaScript code for anonymous function which construct plugin regexp given plugin_id. It should be marked as safe to prevent escaping.
+
 Directory Layout
 ~~~~~~~~~~~~~~~~