From 8c4c26864103890897ca63708867621f16938a25 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 20 May 2014 11:23:36 +0100 Subject: [PATCH 01/51] Added *args, *kwargs paramters to serve/get_context/get_template This makes it easier to pass parameters from the route method down to get_context/get_template --- wagtail/wagtailcore/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 441c74a28..fdf9e7b9a 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -394,23 +394,23 @@ class Page(MP_Node, ClusterableModel, Indexed): return revision.as_page_object() - def get_context(self, request): + def get_context(self, request, *args, **kwargs): return { 'self': self, 'request': request, } - def get_template(self, request): + def get_template(self, request, *args, **kwargs): if request.is_ajax(): return self.ajax_template or self.template else: return self.template - def serve(self, request): + def serve(self, request, *args, **kwargs): return TemplateResponse( request, - self.get_template(request), - self.get_context(request) + self.get_template(request, *args, **kwargs), + self.get_context(request, *args, **kwargs) ) def is_navigable(self): From 217438ca37cee81ff2a27398db4ce1cb69c9829d Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 27 May 2014 14:29:45 +0100 Subject: [PATCH 02/51] Removed more section of wagtailadmin menu. Fixes #228 --- .../wagtailadmin/static/wagtailadmin/js/core.js | 11 ----------- .../static/wagtailadmin/scss/core.scss | 17 ----------------- .../templates/wagtailadmin/shared/main_nav.html | 14 -------------- .../templatetags/wagtailadmin_tags.py | 11 ----------- 4 files changed, 53 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js index 380c3d179..85c4ca31e 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js @@ -93,17 +93,6 @@ $(function(){ }); }); - $(".nav-main .more > a").bind('click keydown', function(){ - var currentAlt = $(this).data('altstate'); - var newAlt = $(this).html(); - - $(this).html(currentAlt); - $(this).data('altstate', newAlt); - $(this).toggleClass('icon-arrow-up icon-arrow-down'); - $(this).parent().find('ul').toggle('fast'); - return false; - }); - $('#menu-search input').bind('focus', function(){ $('#menu-search').addClass('focussed'); }).bind('blur', function(){ diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss index e622680bd..08dbf11ca 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss @@ -226,19 +226,6 @@ img{ } } - .more{ - border:0; - - > a{ - &:before{ - margin-right:0.4em; - } - font-size:0.8em; - padding:0.2em 1.2em; - background-color:$color-grey-1-1; - } - } - .avatar{ display:none; } @@ -312,10 +299,6 @@ img{ } } - .js .nav-main .more ul{ - display:none; - } - .explorer{ position:absolute; margin-top:70px; diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html b/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html index f7eb6528a..ce06d895c 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html @@ -10,19 +10,5 @@ {% trans "Log out" %} - {% if request.user.is_superuser %} {# for now, 'More' links will be superuser-only #} -
  • - {% trans 'More' %} - -
  • - {% endif %} - diff --git a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py index 311163ca3..2d85c355e 100644 --- a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py +++ b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py @@ -26,17 +26,6 @@ def explorer_subnav(nodes): } -@register.assignment_tag -def get_wagtailadmin_tab_urls(): - resolver = urlresolvers.get_resolver(None) - return [ - (key, value[2].get("title", key)) - for key, value - in resolver.reverse_dict.items() - if isinstance(key, basestring) and key.startswith('wagtailadmin_tab_') - ] - - @register.inclusion_tag('wagtailadmin/shared/main_nav.html', takes_context=True) def main_nav(context): menu_items = [ From 280bbff9d92e83530aa8c702fbf149f7b41e4cfd Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 27 May 2014 14:37:11 +0100 Subject: [PATCH 03/51] Added search and redirects links to menu using hooks --- wagtail/wagtailredirects/wagtail_hooks.py | 13 +++++++++++++ wagtail/wagtailsearch/wagtail_hooks.py | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/wagtail/wagtailredirects/wagtail_hooks.py b/wagtail/wagtailredirects/wagtail_hooks.py index 22ae97320..4dbe1a028 100644 --- a/wagtail/wagtailredirects/wagtail_hooks.py +++ b/wagtail/wagtailredirects/wagtail_hooks.py @@ -1,11 +1,24 @@ +from django.core import urlresolvers from django.conf.urls import include, url +from django.utils.translation import ugettext_lazy as _ from wagtail.wagtailadmin import hooks from wagtail.wagtailredirects import urls +from wagtail.wagtailadmin.menu import MenuItem + def register_admin_urls(): return [ url(r'^redirects/', include(urls)), ] hooks.register('register_admin_urls', register_admin_urls) + + +def construct_main_menu(request, menu_items): + # TEMPORARY: Only show if the user is a superuser + if request.user.is_superuser: + menu_items.append( + MenuItem(_('Redirects'), urlresolvers.reverse('wagtailredirects_index'), classnames='icon icon-redirect', order=800) + ) +hooks.register('construct_main_menu', construct_main_menu) diff --git a/wagtail/wagtailsearch/wagtail_hooks.py b/wagtail/wagtailsearch/wagtail_hooks.py index b8bbd2c06..1a656c0ef 100644 --- a/wagtail/wagtailsearch/wagtail_hooks.py +++ b/wagtail/wagtailsearch/wagtail_hooks.py @@ -1,11 +1,24 @@ +from django.core import urlresolvers from django.conf.urls import include, url +from django.utils.translation import ugettext_lazy as _ from wagtail.wagtailadmin import hooks from wagtail.wagtailsearch.urls import admin as admin_urls +from wagtail.wagtailadmin.menu import MenuItem + def register_admin_urls(): return [ url(r'^search/', include(admin_urls)), ] hooks.register('register_admin_urls', register_admin_urls) + + +def construct_main_menu(request, menu_items): + # TEMPORARY: Only show if the user is a superuser + if request.user.is_superuser: + menu_items.append( + MenuItem(_('Editors picks'), urlresolvers.reverse('wagtailsearch_editorspicks_index'), classnames='icon icon-pick', order=900) + ) +hooks.register('construct_main_menu', construct_main_menu) From d88a5e596bf2cd6cdf10600c70d23750d86840c4 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 2 Jun 2014 09:36:06 +0100 Subject: [PATCH 04/51] Cleanup wagtailimages backends code --- wagtail/wagtailimages/backends/base.py | 21 +++++++-------------- wagtail/wagtailimages/backends/pillow.py | 7 +++++-- wagtail/wagtailimages/backends/wand.py | 2 +- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/wagtail/wagtailimages/backends/base.py b/wagtail/wagtailimages/backends/base.py index 25b7e882d..43ea977c9 100644 --- a/wagtail/wagtailimages/backends/base.py +++ b/wagtail/wagtailimages/backends/base.py @@ -1,6 +1,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured + class InvalidImageBackendError(ImproperlyConfigured): pass @@ -8,23 +9,22 @@ class InvalidImageBackendError(ImproperlyConfigured): class BaseImageBackend(object): def __init__(self, params): self.quality = getattr(settings, 'IMAGE_COMPRESSION_QUALITY', 85) - + def open_image(self, input_file): """ - Open an image and return the backend specific image object to pass - to other methods. The object return has to have a size attribute + Open an image and return the backend specific image object to pass + to other methods. The object return has to have a size attribute which is a tuple with the width and height of the image and a format attribute with the format of the image. """ raise NotImplementedError('subclasses of BaseImageBackend must provide an open_image() method') - - + def save_image(self, image, output): """ Save the image to the output """ raise NotImplementedError('subclasses of BaseImageBackend must provide a save_image() method') - + def resize(self, image, size): """ resize image to the requested size, using highest quality settings @@ -32,11 +32,9 @@ class BaseImageBackend(object): """ raise NotImplementedError('subclasses of BaseImageBackend must provide an resize() method') - def crop_to_centre(self, image, size): raise NotImplementedError('subclasses of BaseImageBackend must provide a crop_to_centre() method') - def resize_to_max(self, image, size): """ Resize image down to fit within the given dimensions, preserving aspect ratio. @@ -58,10 +56,8 @@ class BaseImageBackend(object): final_size = (target_width, int(original_height * horz_scale)) else: final_size = (int(original_width * vert_scale), target_height) - - return self.resize(image, final_size) - + return self.resize(image, final_size) def resize_to_min(self, image, size): """ @@ -87,7 +83,6 @@ class BaseImageBackend(object): return self.resize(image, final_size) - def resize_to_width(self, image, target_width): """ Resize image down to the given width, preserving aspect ratio. @@ -104,7 +99,6 @@ class BaseImageBackend(object): return self.resize(image, final_size) - def resize_to_height(self, image, target_height): """ Resize image down to the given height, preserving aspect ratio. @@ -121,7 +115,6 @@ class BaseImageBackend(object): return self.resize(image, final_size) - def resize_to_fill(self, image, size): """ Resize down and crop image to fill the given dimensions. Most suitable for thumbnails. diff --git a/wagtail/wagtailimages/backends/pillow.py b/wagtail/wagtailimages/backends/pillow.py index 05d12d291..96976c277 100644 --- a/wagtail/wagtailimages/backends/pillow.py +++ b/wagtail/wagtailimages/backends/pillow.py @@ -1,6 +1,9 @@ -from base import BaseImageBackend +from __future__ import absolute_import + +from .base import BaseImageBackend import PIL.Image + class PillowBackend(BaseImageBackend): def __init__(self, params): super(PillowBackend, self).__init__(params) @@ -32,4 +35,4 @@ class PillowBackend(BaseImageBackend): top = (original_height - final_height) / 2 return image.crop( (left, top, left + final_width, top + final_height) - ) \ No newline at end of file + ) diff --git a/wagtail/wagtailimages/backends/wand.py b/wagtail/wagtailimages/backends/wand.py index 36e352715..0c493eb52 100644 --- a/wagtail/wagtailimages/backends/wand.py +++ b/wagtail/wagtailimages/backends/wand.py @@ -1,9 +1,9 @@ from __future__ import absolute_import from .base import BaseImageBackend - from wand.image import Image + class WandBackend(BaseImageBackend): def __init__(self, params): super(WandBackend, self).__init__(params) From 351b74c94b547b70902bab4a0e6e028738021349 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 2 Jun 2014 09:52:33 +0100 Subject: [PATCH 05/51] Wand image backend: Always clone images before performing oprations on them --- wagtail/wagtailimages/backends/wand.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/wagtail/wagtailimages/backends/wand.py b/wagtail/wagtailimages/backends/wand.py index 0c493eb52..d1a877436 100644 --- a/wagtail/wagtailimages/backends/wand.py +++ b/wagtail/wagtailimages/backends/wand.py @@ -18,8 +18,9 @@ class WandBackend(BaseImageBackend): image.save(file=output) def resize(self, image, size): - image.resize(size[0], size[1]) - return image + new_image = image.clone() + new_image.resize(size[0], size[1]) + return new_image def crop_to_centre(self, image, size): (original_width, original_height) = image.size @@ -34,7 +35,9 @@ class WandBackend(BaseImageBackend): left = (original_width - final_width) / 2 top = (original_height - final_height) / 2 - image.crop( + + new_image = image.clone() + new_image.crop( left=left, top=top, right=left + final_width, bottom=top + final_height ) - return image + return new_image From e438a6d99b9d936c2a7ea730a3d09da102dc9d85 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 2 Jun 2014 10:51:47 +0100 Subject: [PATCH 06/51] Minor wagtailimages cleanup --- wagtail/wagtailimages/models.py | 11 ++++------- wagtail/wagtailimages/tests.py | 16 ++++++++-------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 24fd1f0bf..a2be29486 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -180,27 +180,25 @@ class Filter(models.Model): generate an output image with this filter applied, returning it as another django.core.files.File object """ - backend = get_image_backend(backend_name) - + if not self.method: self._parse_spec_string() - + # If file is closed, open it input_file.open('rb') image = backend.open_image(input_file) file_format = image.format - + method = getattr(backend, self.method_name) image = method(image, self.method_arg) output = StringIO.StringIO() backend.save_image(image, output, file_format) - + # and then close the input file input_file.close() - # generate new filename derived from old one, inserting the filter spec string before the extension input_filename_parts = os.path.basename(input_file.name).split('.') @@ -210,7 +208,6 @@ class Filter(models.Model): output_filename = '.'.join(output_filename_parts) output_file = File(output, name=output_filename) - return output_file diff --git a/wagtail/wagtailimages/tests.py b/wagtail/wagtailimages/tests.py index 00d1245e6..1bc2acc93 100644 --- a/wagtail/wagtailimages/tests.py +++ b/wagtail/wagtailimages/tests.py @@ -84,10 +84,10 @@ class TestRenditions(TestCase): # default backend should be pillow backend = get_image_backend() self.assertTrue(isinstance(backend, PillowBackend)) - + def test_minification(self): rendition = self.image.get_rendition('width-400') - + # Check size self.assertEqual(rendition.width, 400) self.assertEqual(rendition.height, 300) @@ -114,7 +114,7 @@ class TestRenditions(TestCase): # Check that they are the same object self.assertEqual(first_rendition, second_rendition) - + class TestRenditionsWand(TestCase): def setUp(self): @@ -134,18 +134,18 @@ class TestRenditionsWand(TestCase): def test_minification(self): rendition = self.image.get_rendition('width-400') - + # Check size self.assertEqual(rendition.width, 400) self.assertEqual(rendition.height, 300) - + def test_resize_to_max(self): rendition = self.image.get_rendition('max-100x100') - + # Check size self.assertEqual(rendition.width, 100) self.assertEqual(rendition.height, 75) - + def test_resize_to_min(self): rendition = self.image.get_rendition('min-120x120') @@ -160,7 +160,7 @@ class TestRenditionsWand(TestCase): # Check that they are the same object self.assertEqual(first_rendition, second_rendition) - + class TestImageTag(TestCase): def setUp(self): From b5586102ed5ae4bb3f56c33f2dc9de56628fdd2b Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 2 Jun 2014 10:52:34 +0100 Subject: [PATCH 07/51] Cleanup Filter.process_image a bit --- wagtail/wagtailimages/models.py | 37 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index a2be29486..ddde18d29 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -161,15 +161,17 @@ class Filter(models.Model): # and save the results to self.method_name and self.method_arg try: (method_name_simple, method_arg_string) = self.spec.split('-') - self.method_name = Filter.OPERATION_NAMES[method_name_simple] + method_name = Filter.OPERATION_NAMES[method_name_simple] if method_name_simple in ('max', 'min', 'fill'): # method_arg_string is in the form 640x480 (width, height) = [int(i) for i in method_arg_string.split('x')] - self.method_arg = (width, height) + method_arg = (width, height) else: # method_arg_string is a single number - self.method_arg = int(method_arg_string) + method_arg = int(method_arg_string) + + return method_name, method_arg except (ValueError, KeyError): raise ValueError("Invalid image filter spec: %r" % self.spec) @@ -180,36 +182,33 @@ class Filter(models.Model): generate an output image with this filter applied, returning it as another django.core.files.File object """ + # Get image backend backend = get_image_backend(backend_name) - if not self.method: - self._parse_spec_string() - - # If file is closed, open it + # Open image file input_file.open('rb') image = backend.open_image(input_file) file_format = image.format - method = getattr(backend, self.method_name) + # Parse filter spec string + method_name, method_arg = self._parse_spec_string() - image = method(image, self.method_arg) + # Run filter + method_f = getattr(backend, method_name) + image = method_f(image, method_arg) + # Save output = StringIO.StringIO() backend.save_image(image, output, file_format) - # and then close the input file + # Close input file input_file.close() - # generate new filename derived from old one, inserting the filter spec string before the extension - input_filename_parts = os.path.basename(input_file.name).split('.') - filename_without_extension = '.'.join(input_filename_parts[:-1]) - filename_without_extension = filename_without_extension[:60] # trim filename base so that we're well under 100 chars - output_filename_parts = [filename_without_extension, self.spec] + input_filename_parts[-1:] - output_filename = '.'.join(output_filename_parts) + # Generate new filename derived from old one, inserting the filter spec string before the extension + filename_noext, ext = os.path.splitext(input_file.name) + output_filename = '.'.join([filename_noext[:60], self.spec]) + ext - output_file = File(output, name=output_filename) - - return output_file + return File(output, name=output_filename) class AbstractRendition(models.Model): From 12b81cb8570db762c9891bcd3380e8115e5555ed Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 2 Jun 2014 17:06:18 +0100 Subject: [PATCH 08/51] Added page pagination --- .../templates/wagtailadmin/pages/list.html | 22 ++++++++++++++++++- wagtail/wagtailadmin/views/pages.py | 10 +++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/pages/list.html b/wagtail/wagtailadmin/templates/wagtailadmin/pages/list.html index 7078834ae..0c8f16467 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/pages/list.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/pages/list.html @@ -229,4 +229,24 @@

    {% trans "No pages have been created." %}{% if parent_page and parent_page_perms.can_add_subpage %} {% blocktrans %}Why not add one?{% endblocktrans %}{% endif %} {% endif %} - \ No newline at end of file + + +{% if parent_page and pages and pages.paginator %} +

    +{% endif %} \ No newline at end of file diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index 308f6fdd4..64c612a7b 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -33,6 +33,16 @@ def index(request, parent_page_id=None): else: ordering = 'title' + # Pagination + p = request.GET.get('p', 1) + paginator = Paginator(pages, 50) + try: + pages = paginator.page(p) + except PageNotAnInteger: + pages = paginator.page(1) + except EmptyPage: + pages = paginator.page(paginator.num_pages) + return render(request, 'wagtailadmin/pages/index.html', { 'parent_page': parent_page, 'ordering': ordering, From 8b1db37b4474f32802d372f7f42ec6fda0476d31 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 2 Jun 2014 17:10:11 +0100 Subject: [PATCH 09/51] Keep ordering when paginating pages --- wagtail/wagtailadmin/templates/wagtailadmin/pages/list.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/pages/list.html b/wagtail/wagtailadmin/templates/wagtailadmin/pages/list.html index 0c8f16467..7258c5429 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/pages/list.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/pages/list.html @@ -239,12 +239,12 @@ From 414b3684b98da784c8b486ffc081f360921ebe1a Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 2 Jun 2014 17:15:28 +0100 Subject: [PATCH 10/51] Don't paginate if the user is reordering --- wagtail/wagtailadmin/views/pages.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index 64c612a7b..b5779a6ad 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -34,14 +34,15 @@ def index(request, parent_page_id=None): ordering = 'title' # Pagination - p = request.GET.get('p', 1) - paginator = Paginator(pages, 50) - try: - pages = paginator.page(p) - except PageNotAnInteger: - pages = paginator.page(1) - except EmptyPage: - pages = paginator.page(paginator.num_pages) + if ordering != 'ord': + p = request.GET.get('p', 1) + paginator = Paginator(pages, 50) + try: + pages = paginator.page(p) + except PageNotAnInteger: + pages = paginator.page(1) + except EmptyPage: + pages = paginator.page(paginator.num_pages) return render(request, 'wagtailadmin/pages/index.html', { 'parent_page': parent_page, From 1be56484b410cca6f50b5534b48fba400272f46e Mon Sep 17 00:00:00 2001 From: Jeffrey Hearn Date: Mon, 2 Jun 2014 13:47:30 -0400 Subject: [PATCH 11/51] Updated hallo.js library to 1.0.4 from unknown version --- .../static/wagtailadmin/js/vendor/hallo.js | 3975 +++++++++-------- 1 file changed, 2116 insertions(+), 1859 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/vendor/hallo.js b/wagtail/wagtailadmin/static/wagtailadmin/js/vendor/hallo.js index 7ba3ff2c0..af5950fdb 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/js/vendor/hallo.js +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/vendor/hallo.js @@ -1,671 +1,607 @@ -// Generated by CoffeeScript 1.3.3 -(function() { - - $.widget("ncri.hallohtml", { - options: { - editable: null, +/* Hallo 1.0.4 - rich text editor for jQuery UI +* by Henri Bergius and contributors. Available under the MIT license. +* See http://hallojs.org for more information +*/(function() { + (function(jQuery) { + return jQuery.widget('IKS.hallo', { toolbar: null, - uuid: "", - lang: 'en', - dialogOpts: { - autoOpen: false, - width: 600, - height: 'auto', - modal: false, - resizable: true, - draggable: true, - dialogClass: 'htmledit-dialog' + bound: false, + originalContent: '', + previousContent: '', + uuid: '', + selection: null, + _keepActivated: false, + originalHref: null, + options: { + editable: true, + plugins: {}, + toolbar: 'halloToolbarContextual', + parentElement: 'body', + buttonCssClass: null, + toolbarCssClass: null, + toolbarPositionAbove: false, + toolbarOptions: {}, + placeholder: '', + forceStructured: true, + checkTouch: true, + touchScreen: null }, - dialog: null, - buttonCssClass: null - }, - translations: { - en: { - title: 'Edit HTML', - update: 'Update' + _create: function() { + var options, plugin, _ref, + _this = this; + this.id = this._generateUUID(); + if (this.options.checkTouch && this.options.touchScreen === null) { + this.checkTouch(); + } + _ref = this.options.plugins; + for (plugin in _ref) { + options = _ref[plugin]; + if (!jQuery.isPlainObject(options)) { + options = {}; + } + jQuery.extend(options, { + editable: this, + uuid: this.id, + buttonCssClass: this.options.buttonCssClass + }); + jQuery(this.element)[plugin](options); + } + this.element.one('halloactivated', function() { + return _this._prepareToolbar(); + }); + return this.originalContent = this.getContents(); }, - de: { - title: 'HTML bearbeiten', - update: 'Aktualisieren' - } - }, - texts: null, - populateToolbar: function($toolbar) { - var $buttonHolder, $buttonset, id, widget; - widget = this; - this.texts = this.translations[this.options.lang]; - this.options.toolbar = $toolbar; - this.options.dialog = $("
    ").attr('id', "" + this.options.uuid + "-htmledit-dialog"); - $buttonset = $("").addClass(widget.widgetName); - id = "" + this.options.uuid + "-htmledit"; - $buttonHolder = $(''); - $buttonHolder.hallobutton({ - label: this.texts.title, - icon: 'icon-list-alt', - editable: this.options.editable, - command: null, - queryState: false, - uuid: this.options.uuid, - cssClass: this.options.buttonCssClass - }); - $buttonset.append($buttonHolder); - this.button = $buttonHolder; - this.button.click(function() { - if (widget.options.dialog.dialog("isOpen")) { - widget._closeDialog(); + _init: function() { + if (this.options.editable) { + return this.enable(); } else { - widget._openDialog(); + return this.disable(); + } + }, + destroy: function() { + var options, plugin, _ref; + this.disable(); + if (this.toolbar) { + this.toolbar.remove(); + this.element[this.options.toolbar]('destroy'); + } + _ref = this.options.plugins; + for (plugin in _ref) { + options = _ref[plugin]; + jQuery(this.element)[plugin]('destroy'); + } + return jQuery.Widget.prototype.destroy.call(this); + }, + disable: function() { + var _this = this; + this.element.attr("contentEditable", false); + this.element.off("focus", this._activated); + this.element.off("blur", this._deactivated); + this.element.off("keyup paste change", this._checkModified); + this.element.off("keyup", this._keys); + this.element.off("keyup mouseup", this._checkSelection); + this.bound = false; + jQuery(this.element).removeClass('isModified'); + jQuery(this.element).removeClass('inEditMode'); + this.element.parents('a').addBack().each(function(idx, elem) { + var element; + element = jQuery(elem); + if (!element.is('a')) { + return; + } + if (!_this.originalHref) { + return; + } + return element.attr('href', _this.originalHref); + }); + return this._trigger("disabled", null); + }, + enable: function() { + var _this = this; + this.element.parents('a[href]').addBack().each(function(idx, elem) { + var element; + element = jQuery(elem); + if (!element.is('a[href]')) { + return; + } + _this.originalHref = element.attr('href'); + return element.removeAttr('href'); + }); + this.element.attr("contentEditable", true); + if (!jQuery.parseHTML(this.element.html())) { + this.element.html(this.options.placeholder); + jQuery(this.element).addClass('inPlaceholderMode'); + this.element.css({ + 'min-width': this.element.innerWidth(), + 'min-height': this.element.innerHeight() + }); + } + if (!this.bound) { + this.element.on("focus", this, this._activated); + this.element.on("blur", this, this._deactivated); + this.element.on("keyup paste change", this, this._checkModified); + this.element.on("keyup", this, this._keys); + this.element.on("keyup mouseup", this, this._checkSelection); + this.bound = true; + } + if (this.options.forceStructured) { + this._forceStructured(); + } + return this._trigger("enabled", null); + }, + activate: function() { + return this.element.focus(); + }, + containsSelection: function() { + var range; + range = this.getSelection(); + return this.element.has(range.startContainer).length > 0; + }, + getSelection: function() { + var range, sel; + sel = rangy.getSelection(); + range = null; + if (sel.rangeCount > 0) { + range = sel.getRangeAt(0); + } else { + range = rangy.createRange(); + } + return range; + }, + restoreSelection: function(range) { + var sel; + sel = rangy.getSelection(); + return sel.setSingleRange(range); + }, + replaceSelection: function(cb) { + var newTextNode, r, range, sel, t; + if (navigator.appName === 'Microsoft Internet Explorer') { + t = document.selection.createRange().text; + r = document.selection.createRange(); + return r.pasteHTML(cb(t)); + } else { + sel = window.getSelection(); + range = sel.getRangeAt(0); + newTextNode = document.createTextNode(cb(range.extractContents())); + range.insertNode(newTextNode); + range.setStartAfter(newTextNode); + sel.removeAllRanges(); + return sel.addRange(range); + } + }, + removeAllSelections: function() { + if (navigator.appName === 'Microsoft Internet Explorer') { + return range.empty(); + } else { + return window.getSelection().removeAllRanges(); + } + }, + getPluginInstance: function(plugin) { + var instance; + instance = jQuery(this.element).data("IKS-" + plugin); + if (instance) { + return instance; + } + instance = jQuery(this.element).data(plugin); + if (instance) { + return instance; + } + throw new Error("Plugin " + plugin + " not found"); + }, + getContents: function() { + var cleanup, instance, plugin; + for (plugin in this.options.plugins) { + instance = this.getPluginInstance(plugin); + if (!instance) { + continue; + } + cleanup = instance.cleanupContentClone; + if (!jQuery.isFunction(cleanup)) { + continue; + } + jQuery(this.element)[plugin]('cleanupContentClone', this.element); + } + return this.element.html(); + }, + setContents: function(contents) { + return this.element.html(contents); + }, + isModified: function() { + if (!this.previousContent) { + this.previousContent = this.originalContent; + } + return this.previousContent !== this.getContents(); + }, + setUnmodified: function() { + jQuery(this.element).removeClass('isModified'); + return this.previousContent = this.getContents(); + }, + setModified: function() { + jQuery(this.element).addClass('isModified'); + return this._trigger('modified', null, { + editable: this, + content: this.getContents() + }); + }, + restoreOriginalContent: function() { + return this.element.html(this.originalContent); + }, + execute: function(command, value) { + if (document.execCommand(command, false, value)) { + return this.element.trigger("change"); + } + }, + protectFocusFrom: function(el) { + var _this = this; + return el.on("mousedown", function(event) { + event.preventDefault(); + _this._protectToolbarFocus = true; + return setTimeout(function() { + return _this._protectToolbarFocus = false; + }, 300); + }); + }, + keepActivated: function(_keepActivated) { + this._keepActivated = _keepActivated; + }, + _generateUUID: function() { + var S4; + S4 = function() { + return ((1 + Math.random()) * 0x10000 | 0).toString(16).substring(1); + }; + return "" + (S4()) + (S4()) + "-" + (S4()) + "-" + (S4()) + "-" + (S4()) + "-" + (S4()) + (S4()) + (S4()); + }, + _prepareToolbar: function() { + var defaults, instance, plugin, populate, toolbarOptions; + this.toolbar = jQuery('
    ').hide(); + if (this.options.toolbarCssClass) { + this.toolbar.addClass(this.options.toolbarCssClass); + } + defaults = { + editable: this, + parentElement: this.options.parentElement, + toolbar: this.toolbar, + positionAbove: this.options.toolbarPositionAbove + }; + toolbarOptions = jQuery.extend({}, defaults, this.options.toolbarOptions); + this.element[this.options.toolbar](toolbarOptions); + for (plugin in this.options.plugins) { + instance = this.getPluginInstance(plugin); + if (!instance) { + continue; + } + populate = instance.populateToolbar; + if (!jQuery.isFunction(populate)) { + continue; + } + this.element[plugin]('populateToolbar', this.toolbar); + } + this.element[this.options.toolbar]('setPosition'); + return this.protectFocusFrom(this.toolbar); + }, + changeToolbar: function(element, toolbar, hide) { + var originalToolbar; + if (hide == null) { + hide = false; + } + originalToolbar = this.options.toolbar; + this.options.parentElement = element; + if (toolbar) { + this.options.toolbar = toolbar; + } + if (!this.toolbar) { + return; + } + this.element[originalToolbar]('destroy'); + this.toolbar.remove(); + this._prepareToolbar(); + if (hide) { + return this.toolbar.hide(); + } + }, + _checkModified: function(event) { + var widget; + widget = event.data; + if (widget.isModified()) { + return widget.setModified(); + } + }, + _keys: function(event) { + var old, widget; + widget = event.data; + if (event.keyCode === 27) { + old = widget.getContents(); + widget.restoreOriginalContent(event); + widget._trigger("restored", null, { + editable: widget, + content: widget.getContents(), + thrown: old + }); + return widget.turnOff(); + } + }, + _rangesEqual: function(r1, r2) { + if (r1.startContainer !== r2.startContainer) { + return false; + } + if (r1.startOffset !== r2.startOffset) { + return false; + } + if (r1.endContainer !== r2.endContainer) { + return false; + } + if (r1.endOffset !== r2.endOffset) { + return false; + } + return true; + }, + _checkSelection: function(event) { + var widget; + if (event.keyCode === 27) { + return; + } + widget = event.data; + return setTimeout(function() { + var sel; + sel = widget.getSelection(); + if (widget._isEmptySelection(sel) || widget._isEmptyRange(sel)) { + if (widget.selection) { + widget.selection = null; + widget._trigger("unselected", null, { + editable: widget, + originalEvent: event + }); + } + return; + } + if (!widget.selection || !widget._rangesEqual(sel, widget.selection)) { + widget.selection = sel.cloneRange(); + return widget._trigger("selected", null, { + editable: widget, + selection: widget.selection, + ranges: [widget.selection], + originalEvent: event + }); + } + }, 0); + }, + _isEmptySelection: function(selection) { + if (selection.type === "Caret") { + return true; } return false; - }); - this.options.editable.element.on("hallodeactivated", function() { - return widget._closeDialog(); - }); - $toolbar.append($buttonset); - this.options.dialog.dialog(this.options.dialogOpts); - return this.options.dialog.dialog("option", "title", this.texts.title); - }, - _openDialog: function() { - var $editableEl, html, widget, xposition, yposition, - _this = this; - widget = this; - $editableEl = $(this.options.editable.element); - xposition = $editableEl.offset().left + $editableEl.outerWidth() + 10; - yposition = this.options.toolbar.offset().top - $(document).scrollTop(); - this.options.dialog.dialog("option", "position", [xposition, yposition]); - this.options.editable.keepActivated(true); - this.options.dialog.dialog("open"); - this.options.dialog.on('dialogclose', function() { - $('label', _this.button).removeClass('ui-state-active'); - _this.options.editable.element.focus(); - return _this.options.editable.keepActivated(false); - }); - this.options.dialog.html($("