about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2017-07-02 13:03:43 -0400
committerKelly Rauchenberger <fefferburbia@gmail.com>2017-07-02 13:03:43 -0400
commite47e83cf6bded3d1924b4d500193e7876833ef83 (patch)
tree058f011637e67455dcd8451fbfa784b5883c6f69
parent528ccde8915cd1ed7a39e137dd4d98869797956a (diff)
downloadthoughts-e47e83cf6bded3d1924b4d500193e7876833ef83.tar.gz
thoughts-e47e83cf6bded3d1924b4d500193e7876833ef83.tar.bz2
thoughts-e47e83cf6bded3d1924b4d500193e7876833ef83.zip
Created admin panel
Currently allows you to create and edit blogs, including associated
records. Uses a WYSIWYG editor that allows uploading images.

Also included jQuery :(
-rw-r--r--.gitignore1
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock20
-rw-r--r--app/assets/javascripts/admin/dashboard.coffee3
-rw-r--r--app/assets/javascripts/application.js4
-rw-r--r--app/assets/javascripts/ckeditor/config.js61
-rw-r--r--app/assets/javascripts/ckeditor/contents.css208
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/dev/assets/image1.jpgbin0 -> 34316 bytes
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/dev/assets/image2.jpgbin0 -> 27711 bytes
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/dev/contents.css35
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/dev/image2.html339
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/dialogs/image2.js553
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/icons/hidpi/image.pngbin0 -> 905 bytes
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/icons/image.pngbin0 -> 498 bytes
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/af.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ar.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/az.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/bg.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/bn.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/bs.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ca.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/cs.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/cy.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/da.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/de-ch.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/de.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/el.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/en-au.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/en-ca.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/en-gb.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/en.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/eo.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/es-mx.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/es.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/et.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/eu.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/fa.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/fi.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/fo.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/fr-ca.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/fr.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/gl.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/gu.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/he.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/hi.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/hr.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/hu.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/id.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/is.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/it.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ja.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ka.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/km.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ko.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ku.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/lt.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/lv.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/mk.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/mn.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ms.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/nb.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/nl.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/no.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/oc.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/pl.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/pt-br.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/pt.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ro.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ru.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/si.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/sk.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/sl.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/sq.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/sr-latn.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/sr.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/sv.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/th.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/tr.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/tt.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/ug.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/uk.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/vi.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/zh-cn.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/lang/zh.js21
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/plugin.js1712
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/samples/assets/image1.jpgbin0 -> 33171 bytes
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/samples/assets/image2.jpgbin0 -> 26238 bytes
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/samples/image2.html68
-rw-r--r--app/assets/javascripts/ckeditor/plugins/lineutils/dev/dnd.html172
-rw-r--r--app/assets/javascripts/ckeditor/plugins/lineutils/dev/magicfinger.html285
-rw-r--r--app/assets/javascripts/ckeditor/plugins/lineutils/plugin.js1018
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/dev/assets/contents.css23
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/dev/assets/sample.jpgbin0 -> 17932 bytes
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/contents.css36
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js51
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/icons/simplebox.pngbin0 -> 286 bytes
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/plugin.js114
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/dev/console.js131
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/dev/nestedwidgets.html134
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/dev/widgetstyles.html144
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/images/handle.pngbin0 -> 220 bytes
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/af.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/ar.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/az.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/bg.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/ca.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/cs.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/cy.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/da.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/de-ch.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/de.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/el.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/en-gb.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/en.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/eo.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/es-mx.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/es.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/eu.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/fa.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/fi.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/fr.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/gl.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/he.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/hr.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/hu.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/id.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/it.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/ja.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/km.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/ko.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/ku.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/lv.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/nb.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/nl.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/no.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/oc.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/pl.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/pt-br.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/pt.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/ru.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/sk.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/sl.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/sq.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/sv.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/tr.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/tt.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/ug.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/uk.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/vi.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/zh-cn.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/lang/zh.js8
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widget/plugin.js4126
-rw-r--r--app/assets/javascripts/ckeditor/plugins/widgetselection/plugin.js366
-rw-r--r--app/assets/javascripts/init_ckeditor.coffee6
-rw-r--r--app/assets/javascripts/records.coffee6
-rw-r--r--app/assets/stylesheets/admin.css.scss4
-rw-r--r--app/assets/stylesheets/admin/dashboard.scss3
-rw-r--r--app/assets/stylesheets/admin/layout.scss190
-rw-r--r--app/assets/stylesheets/main/layout.scss2
-rw-r--r--app/controllers/admin/admin_controller.rb5
-rw-r--r--app/controllers/admin/blogs_controller.rb52
-rw-r--r--app/controllers/admin/dashboard_controller.rb12
-rw-r--r--app/controllers/entries_controller.rb21
-rw-r--r--app/helpers/admin/admin_helper.rb11
-rw-r--r--app/helpers/admin/blogs_helper.rb2
-rw-r--r--app/helpers/admin/dashboard_helper.rb2
-rw-r--r--app/models/ckeditor/asset.rb4
-rw-r--r--app/models/ckeditor/attachment_file.rb13
-rw-r--r--app/models/ckeditor/picture.rb14
-rw-r--r--app/models/entry.rb4
-rw-r--r--app/views/admin/blogs/_form.html.haml23
-rw-r--r--app/views/admin/blogs/edit.html.haml2
-rw-r--r--app/views/admin/blogs/index.html.haml10
-rw-r--r--app/views/admin/blogs/new.html.haml2
-rw-r--r--app/views/admin/dashboard/index.html.haml1
-rw-r--r--app/views/entries/edit.html.haml11
-rw-r--r--app/views/layouts/admin.html.haml27
-rw-r--r--app/views/layouts/application.html.haml1
-rw-r--r--config/initializers/assets.rb3
-rw-r--r--config/initializers/ckeditor.rb58
-rw-r--r--config/routes.rb14
-rw-r--r--db/migrate/20170629184901_create_ckeditor_assets.rb23
-rw-r--r--db/schema.rb15
-rw-r--r--test/controllers/admin/blogs_controller_test.rb19
-rw-r--r--test/controllers/admin/dashboard_controller_test.rb9
185 files changed, 12003 insertions, 43 deletions
diff --git a/.gitignore b/.gitignore index 33232cd..dad415a 100644 --- a/.gitignore +++ b/.gitignore
@@ -26,3 +26,4 @@ tags
26.byebug_history 26.byebug_history
27.DS_Store 27.DS_Store
28*.swo 28*.swo
29/public/uploads
diff --git a/Gemfile b/Gemfile index 32a78dc..69deff3 100644 --- a/Gemfile +++ b/Gemfile
@@ -56,3 +56,6 @@ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
56gem 'haml' 56gem 'haml'
57gem 'normalize-rails' 57gem 'normalize-rails'
58gem 'devise' 58gem 'devise'
59gem 'ckeditor'
60gem 'paperclip'
61gem 'jquery-rails'
diff --git a/Gemfile.lock b/Gemfile.lock index 41f4d51..857547f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock
@@ -54,6 +54,12 @@ GEM
54 xpath (~> 2.0) 54 xpath (~> 2.0)
55 childprocess (0.7.0) 55 childprocess (0.7.0)
56 ffi (~> 1.0, >= 1.0.11) 56 ffi (~> 1.0, >= 1.0.11)
57 ckeditor (4.2.4)
58 cocaine
59 orm_adapter (~> 0.5.0)
60 climate_control (0.2.0)
61 cocaine (0.5.8)
62 climate_control (>= 0.0.3, < 1.0)
57 coffee-rails (4.2.2) 63 coffee-rails (4.2.2)
58 coffee-script (>= 2.2.0) 64 coffee-script (>= 2.2.0)
59 railties (>= 4.0.0) 65 railties (>= 4.0.0)
@@ -80,6 +86,10 @@ GEM
80 jbuilder (2.7.0) 86 jbuilder (2.7.0)
81 activesupport (>= 4.2.0) 87 activesupport (>= 4.2.0)
82 multi_json (>= 1.2) 88 multi_json (>= 1.2)
89 jquery-rails (4.3.1)
90 rails-dom-testing (>= 1, < 3)
91 railties (>= 4.2.0)
92 thor (>= 0.14, < 2.0)
83 listen (3.1.5) 93 listen (3.1.5)
84 rb-fsevent (~> 0.9, >= 0.9.4) 94 rb-fsevent (~> 0.9, >= 0.9.4)
85 rb-inotify (~> 0.9, >= 0.9.7) 95 rb-inotify (~> 0.9, >= 0.9.7)
@@ -92,6 +102,7 @@ GEM
92 mime-types (3.1) 102 mime-types (3.1)
93 mime-types-data (~> 3.2015) 103 mime-types-data (~> 3.2015)
94 mime-types-data (3.2016.0521) 104 mime-types-data (3.2016.0521)
105 mimemagic (0.3.2)
95 mini_portile2 (2.2.0) 106 mini_portile2 (2.2.0)
96 minitest (5.10.2) 107 minitest (5.10.2)
97 multi_json (1.12.1) 108 multi_json (1.12.1)
@@ -100,6 +111,12 @@ GEM
100 mini_portile2 (~> 2.2.0) 111 mini_portile2 (~> 2.2.0)
101 normalize-rails (4.1.1) 112 normalize-rails (4.1.1)
102 orm_adapter (0.5.0) 113 orm_adapter (0.5.0)
114 paperclip (5.1.0)
115 activemodel (>= 4.2.0)
116 activesupport (>= 4.2.0)
117 cocaine (~> 0.5.5)
118 mime-types
119 mimemagic (~> 0.3.0)
103 public_suffix (2.0.5) 120 public_suffix (2.0.5)
104 puma (3.9.1) 121 puma (3.9.1)
105 rack (2.0.3) 122 rack (2.0.3)
@@ -190,12 +207,15 @@ PLATFORMS
190DEPENDENCIES 207DEPENDENCIES
191 byebug 208 byebug
192 capybara (~> 2.13) 209 capybara (~> 2.13)
210 ckeditor
193 coffee-rails (~> 4.2) 211 coffee-rails (~> 4.2)
194 devise 212 devise
195 haml 213 haml
196 jbuilder (~> 2.5) 214 jbuilder (~> 2.5)
215 jquery-rails
197 listen (>= 3.0.5, < 3.2) 216 listen (>= 3.0.5, < 3.2)
198 normalize-rails 217 normalize-rails
218 paperclip
199 puma (~> 3.7) 219 puma (~> 3.7)
200 rails (~> 5.1.1) 220 rails (~> 5.1.1)
201 sass-rails (~> 5.0) 221 sass-rails (~> 5.0)
diff --git a/app/assets/javascripts/admin/dashboard.coffee b/app/assets/javascripts/admin/dashboard.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/admin/dashboard.coffee
@@ -0,0 +1,3 @@
1# Place all the behaviors and hooks related to the matching controller here.
2# All this logic will automatically be available in application.js.
3# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 46b2035..499c820 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js
@@ -10,6 +10,8 @@
10// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 10// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11// about supported directives. 11// about supported directives.
12// 12//
13//= require rails-ujs 13//= require jquery3
14//= require jquery_ujs
14//= require turbolinks 15//= require turbolinks
16//= require ckeditor/init
15//= require_tree . 17//= require_tree .
diff --git a/app/assets/javascripts/ckeditor/config.js b/app/assets/javascripts/ckeditor/config.js new file mode 100644 index 0000000..44b3c23 --- /dev/null +++ b/app/assets/javascripts/ckeditor/config.js
@@ -0,0 +1,61 @@
1/*
2Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.html or http://ckeditor.com/license
4*/
5
6CKEDITOR.editorConfig = function( config )
7{
8 // Define changes to default configuration here. For example:
9 // config.language = 'fr';
10 // config.uiColor = '#AADC6E';
11 config.height = "512px";
12
13 config.extraPlugins = "lineutils,widgetselection,widget,image2";
14
15 /* Filebrowser routes */
16 // The location of an external file browser, that should be launched when "Browse Server" button is pressed.
17 config.filebrowserBrowseUrl = "/ckeditor/attachment_files";
18
19 // The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Flash dialog.
20 config.filebrowserFlashBrowseUrl = "/ckeditor/attachment_files";
21
22 // The location of a script that handles file uploads in the Flash dialog.
23 config.filebrowserFlashUploadUrl = "/ckeditor/attachment_files";
24
25 // The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Link tab of Image dialog.
26 config.filebrowserImageBrowseLinkUrl = "/ckeditor/pictures";
27
28 // The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Image dialog.
29 config.filebrowserImageBrowseUrl = "/ckeditor/pictures";
30
31 // The location of a script that handles file uploads in the Image dialog.
32 config.filebrowserImageUploadUrl = "/ckeditor/pictures?";
33
34 // The location of a script that handles file uploads.
35 config.filebrowserUploadUrl = "/ckeditor/attachment_files";
36
37 config.allowedContent = true;
38
39 // Toolbar groups configuration.
40 config.toolbar = [
41 { name: 'document', groups: [ 'mode', 'document', 'doctools' ], items: [ 'Source', 'Maximize' ] },
42 { name: 'clipboard', groups: [ 'clipboard', 'undo' ], items: [ 'Cut', 'Copy', 'Paste', 'PasteText', '-', 'Undo', 'Redo' ] },
43 { name: 'editing', groups: [ 'find' ], items: [ 'Find', 'Replace' ] },
44 // { name: 'forms', items: [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
45 { name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
46 { name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule' ] },
47 { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ], items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock' ] },
48 '/',
49 { name: 'styles', items: [ 'Styles', 'Format', 'Font', 'FontSize' ] },
50 { name: 'colors', items: [ 'TextColor', 'BGColor' ] },
51 { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ], items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] }
52 ];
53
54 config.toolbar_mini = [
55 { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ], items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock' ] },
56 { name: 'styles', items: [ 'Font', 'FontSize' ] },
57 { name: 'colors', items: [ 'TextColor', 'BGColor' ] },
58 { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ], items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] },
59 { name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule', 'SpecialChar' ] }
60 ];
61};
diff --git a/app/assets/javascripts/ckeditor/contents.css b/app/assets/javascripts/ckeditor/contents.css new file mode 100644 index 0000000..920f2ca --- /dev/null +++ b/app/assets/javascripts/ckeditor/contents.css
@@ -0,0 +1,208 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5
6body
7{
8 /* Font */
9 font-family: sans-serif, Arial, Verdana, "Trebuchet MS";
10 font-size: 12px;
11
12 /* Text color */
13 color: #333;
14
15 /* Remove the background color to make it transparent */
16 background-color: #fff;
17
18 margin: 20px;
19}
20
21.cke_editable
22{
23 font-size: 13px;
24 line-height: 1.6;
25
26 /* Fix for missing scrollbars with RTL texts. (#10488) */
27 word-wrap: break-word;
28}
29
30blockquote
31{
32 font-style: italic;
33 font-family: Georgia, Times, "Times New Roman", serif;
34 padding: 2px 0;
35 border-style: solid;
36 border-color: #ccc;
37 border-width: 0;
38}
39
40.cke_contents_ltr blockquote
41{
42 padding-left: 20px;
43 padding-right: 8px;
44 border-left-width: 5px;
45}
46
47.cke_contents_rtl blockquote
48{
49 padding-left: 8px;
50 padding-right: 20px;
51 border-right-width: 5px;
52}
53
54a
55{
56 color: #0782C1;
57}
58
59ol,ul,dl
60{
61 /* IE7: reset rtl list margin. (#7334) */
62 *margin-right: 0px;
63 /* preserved spaces for list items with text direction other than the list. (#6249,#8049)*/
64 padding: 0 40px;
65}
66
67h1,h2,h3,h4,h5,h6
68{
69 font-weight: normal;
70 line-height: 1.2;
71}
72
73hr
74{
75 border: 0px;
76 border-top: 1px solid #ccc;
77}
78
79img.right
80{
81 border: 1px solid #ccc;
82 float: right;
83 margin-left: 15px;
84 padding: 5px;
85}
86
87img.left
88{
89 border: 1px solid #ccc;
90 float: left;
91 margin-right: 15px;
92 padding: 5px;
93}
94
95pre
96{
97 white-space: pre-wrap; /* CSS 2.1 */
98 word-wrap: break-word; /* IE7 */
99 -moz-tab-size: 4;
100 tab-size: 4;
101}
102
103.marker
104{
105 background-color: Yellow;
106}
107
108span[lang]
109{
110 font-style: italic;
111}
112
113figure
114{
115 text-align: center;
116 border: solid 1px #ccc;
117 border-radius: 2px;
118 background: rgba(0,0,0,0.05);
119 padding: 10px;
120 margin: 10px 20px;
121 display: inline-block;
122}
123
124figure > figcaption
125{
126 text-align: center;
127 display: block; /* For IE8 */
128}
129
130a > img {
131 padding: 1px;
132 margin: 1px;
133 border: none;
134 outline: 1px solid #0782C1;
135}
136
137/* Widget Styles */
138.code-featured
139{
140 border: 5px solid red;
141}
142
143.math-featured
144{
145 padding: 20px;
146 box-shadow: 0 0 2px rgba(200, 0, 0, 1);
147 background-color: rgba(255, 0, 0, 0.05);
148 margin: 10px;
149}
150
151.image-clean
152{
153 border: 0;
154 background: none;
155 padding: 0;
156}
157
158.image-clean > figcaption
159{
160 font-size: .9em;
161 text-align: right;
162}
163
164.image-grayscale
165{
166 background-color: white;
167 color: #666;
168}
169
170.image-grayscale img, img.image-grayscale
171{
172 filter: grayscale(100%);
173}
174
175.embed-240p
176{
177 max-width: 426px;
178 max-height: 240px;
179 margin:0 auto;
180}
181
182.embed-360p
183{
184 max-width: 640px;
185 max-height: 360px;
186 margin:0 auto;
187}
188
189.embed-480p
190{
191 max-width: 854px;
192 max-height: 480px;
193 margin:0 auto;
194}
195
196.embed-720p
197{
198 max-width: 1280px;
199 max-height: 720px;
200 margin:0 auto;
201}
202
203.embed-1080p
204{
205 max-width: 1920px;
206 max-height: 1080px;
207 margin:0 auto;
208}
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/dev/assets/image1.jpg b/app/assets/javascripts/ckeditor/plugins/image2/dev/assets/image1.jpg new file mode 100644 index 0000000..2fe79eb --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/dev/assets/image1.jpg
Binary files differ
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/dev/assets/image2.jpg b/app/assets/javascripts/ckeditor/plugins/image2/dev/assets/image2.jpg new file mode 100644 index 0000000..453ece5 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/dev/assets/image2.jpg
Binary files differ
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/dev/contents.css b/app/assets/javascripts/ckeditor/plugins/image2/dev/contents.css new file mode 100644 index 0000000..ef29f02 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/dev/contents.css
@@ -0,0 +1,35 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5
6.cke_widget_wrapper:hover:after {
7 content: "id: " attr(data-cke-widget-id);
8 position: absolute;
9 top: 0;
10 right: 0;
11 padding: 2px 4px;
12 background: #EEE;
13 border: solid 1px #DDD;
14 border-radius: 2px;
15 color: #BBB;
16 font: bold 10px sans-serif;
17}
18
19.align-left {
20 float: left;
21 margin-right: 20px;
22}
23
24.align-right {
25 float: right;
26 margin-left: 20px;
27}
28
29.align-center {
30 text-align: center;
31}
32
33.align-center > figure {
34 display: inline-block;
35}
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/dev/image2.html b/app/assets/javascripts/ckeditor/plugins/image2/dev/image2.html new file mode 100644 index 0000000..1070a6e --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/dev/image2.html
@@ -0,0 +1,339 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
4For licensing, see LICENSE.md or http://ckeditor.com/license
5-->
6<html>
7<head>
8 <meta charset="utf-8">
9 <title>Widget Image &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <script src="../../../dev/console/console.js"></script>
12 <script src="../../../dev/console/focusconsole.js"></script>
13 <script src="../../widget/dev/console.js"></script>
14 <script>
15 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
16 CKEDITOR.tools.enableHtml5Elements( document );
17
18 var editor;
19
20 // The instanceReady event is fired, when an instance of CKEditor has finished
21 // its initialization.
22 CKEDITOR.on( 'instanceReady', function( ev ) {
23 editor = ev.editor;
24
25 // Show this "on" button.
26 document.getElementById( 'readOnlyOn' ).style.display = '';
27
28 // Event fired when the readOnly property changes.
29 editor.on( 'readOnly', function() {
30 document.getElementById( 'readOnlyOn' ).style.display = this.readOnly ? 'none' : '';
31 document.getElementById( 'readOnlyOff' ).style.display = this.readOnly ? '' : 'none';
32 });
33 });
34
35 function toggleReadOnly( isReadOnly ) {
36 // Change the read-only state of the editor.
37 // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setReadOnly
38 editor.setReadOnly( isReadOnly );
39 }
40
41 </script>
42 <link href="../../../samples/old/sample.css" rel="stylesheet">
43
44 <style>
45
46 body {
47 font-size: 13px;
48 }
49 .editable {
50 padding: 20px;
51 border: 2px solid #dfdfdf;
52 overflow: auto;
53 }
54
55 body p {
56 line-height: 1.8em;
57 }
58
59 /* Reset some styles from sample.css */
60 .cke_editable.cke_editable_inline
61 {
62 cursor: auto;
63 }
64 .cke_editable.cke_editable_inline.cke_focus
65 {
66 box-shadow: none;
67 background: inherit;
68 cursor: auto;
69 }
70
71 </style>
72 <link href="contents.css" rel="stylesheet">
73 <link href="../../../contents.css" rel="stylesheet">
74</head>
75<body>
76 <h1 class="samples">
77 <a href="../../../samples/old/index.html">CKEditor Samples</a> &raquo; Widget Image
78 </h1>
79
80 <h2>Classic (iframe-based) Sample</h2>
81
82 <textarea id="editor1" cols="10" rows="10">
83 <h1>Apollo 11</h1>
84
85 <figure class="image" style="float: right">
86 <img alt="Saturn V" src="assets/image1.jpg" width="200" data-foo="*********" data-bar="@@@@@@@@" />
87 <figcaption>Roll out of Saturn V on launch pad</figcaption>
88 </figure>
89
90 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.</p>
91
92 <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
93
94 <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
95
96 <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
97
98 <blockquote>
99 <p>One small step for [a] man, one giant leap for mankind.</p>
100 </blockquote>
101
102 <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p>
103
104 <blockquote>
105 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
106 </blockquote>
107
108 <figure class="image" style="float: right">
109 <img alt="The Eagle" src="assets/image2.jpg" style="width: 200px" />
110 <figcaption>The Eagle in lunar orbit</figcaption>
111 </figure>
112
113 <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
114
115 <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
116
117 <ol>
118 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
119 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
120 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
121 </ol>
122
123 <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
124
125 <p style="text-align:center">
126 <img alt="Saturn V" src="assets/image1.jpg" width="200" />
127 </p>
128
129 <hr />
130 <p style="text-align:right"><small>Source: <a href="http://en.wikipedia.org/wiki/Apollo_11">Wikipedia.org</a></small></p>
131 </textarea>
132
133 <h2>Inline Sample</h2>
134
135 <div id="editor2" contenteditable="true" class="editable">
136 <h2>Apollo 11</h2>
137
138 <figure class="image" style="float: right">
139 <img alt="Saturn V" src="assets/image1.jpg" width="200" />
140 <figcaption>Roll out of Saturn V on launch pad</figcaption>
141 </figure>
142
143 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.</p>
144
145 <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
146
147 <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
148
149 <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
150
151 <blockquote>
152 <p>One small step for [a] man, one giant leap for mankind.</p>
153 </blockquote>
154
155 <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p>
156
157 <blockquote>
158 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
159 </blockquote>
160
161 <figure class="image" style="float: right">
162 <img alt="The Eagle" src="assets/image2.jpg" style="width: 200px" />
163 <figcaption>The Eagle in lunar orbit</figcaption>
164 </figure>
165
166 <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
167
168 <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
169
170 <ol>
171 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
172 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
173 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
174 </ol>
175
176 <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
177
178 <p style="text-align:center">
179 <img alt="Saturn V" src="assets/image1.jpg" width="200" />
180 </p>
181
182 <hr />
183 <p style="text-align:right"><small>Source: <a href="http://en.wikipedia.org/wiki/Apollo_11">Wikipedia.org</a></small></p>
184 </div>
185
186 <h2>Div Editing Area Sample</h2>
187
188 <textarea id="editor3" cols="10" rows="10">
189 <h1>Apollo 11</h1>
190
191 <figure class="caption" style="float: right">
192 <img alt="Saturn V" src="assets/image1.jpg" width="200" />
193 <figcaption>Roll out of Saturn V on launch pad</figcaption>
194 </figure>
195
196 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.</p>
197
198 <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
199
200 <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
201
202 <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
203
204 <blockquote>
205 <p>One small step for [a] man, one giant leap for mankind.</p>
206 </blockquote>
207
208 <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p>
209
210 <blockquote>
211 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
212 </blockquote>
213
214 <figure class="caption" style="float: right">
215 <img alt="The Eagle" src="assets/image2.jpg" style="width: 200px" />
216 <figcaption>The Eagle in lunar orbit</figcaption>
217 </figure>
218
219 <h2>Technical Details <a id="tech-details" name="tech-details"></a></h2>
220
221 <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
222
223 <ol>
224 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
225 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
226 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
227 </ol>
228
229 <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
230
231 <p style="text-align:center">
232 <img alt="Saturn V" src="assets/image1.jpg" width="200" />
233 </p>
234
235 <hr />
236 <p style="text-align:right"><small>Source: <a href="http://en.wikipedia.org/wiki/Apollo_11">Wikipedia.org</a></small></p>
237 </textarea>
238
239 <h2>alignClasses samples</h2>
240
241 <textarea id="editor4" cols="10" rows="10">
242 <h1>Apollo 11</h1>
243
244 <figure class="align-left image">
245 <img alt="Saturn V" src="assets/image1.jpg" width="200" data-foo="*********" data-bar="@@@@@@@@" />
246 <figcaption>Roll out of Saturn V on launch pad</figcaption>
247 </figure>
248
249 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.</p>
250
251 <blockquote>
252 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
253 </blockquote>
254
255 <figure class="align-right image">
256 <img alt="The Eagle" src="assets/image2.jpg" style="width: 200px" />
257 <figcaption>The Eagle in lunar orbit</figcaption>
258 </figure>
259
260 <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
261
262 <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
263
264 <ol>
265 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
266 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
267 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
268 </ol>
269
270 <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
271
272 <p class="align-center">
273 <img alt="Saturn V" src="assets/image1.jpg" width="200" />
274 </p>
275
276 <hr />
277 <p style="text-align:right"><small>Source: <a href="http://en.wikipedia.org/wiki/Apollo_11">Wikipedia.org</a></small></p>
278 </textarea>
279
280 <p>
281 <input id="readOnlyOn" onclick="toggleReadOnly( true );" type="button" value="Make it read-only" style="display:none">
282 <input id="readOnlyOff" onclick="toggleReadOnly( false );" type="button" value="Make it editable again" style="display:none">
283 </p>
284
285 <script>
286
287 CKEDITOR.disableAutoInline = true;
288
289 CKEDITOR.replace( 'editor1', {
290 extraPlugins: 'image2',
291 height: 600,
292 contentsCss: [ '../../../contents.css', 'contents.css' ],
293 extraAllowedContent: 'img[data-foo,data-bar]',
294
295 filebrowserBrowseUrl: '/ckfinder/ckfinder.html',
296 filebrowserImageBrowseUrl: '/ckfinder/ckfinder.html?Type=Images',
297 filebrowserUploadUrl: '/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Files',
298 filebrowserImageUploadUrl: '/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images',
299 } );
300
301 CKEDITOR.inline( 'editor2', {
302 extraPlugins: 'image2,sourcedialog'
303 } );
304
305 CKEDITOR.replace( 'editor3', {
306 extraPlugins: 'image2,divarea',
307 height: 600
308 } );
309
310 CKEDITOR.replace( 'editor4', {
311 extraPlugins: 'image2',
312 image2_alignClasses: [ 'align-left', 'align-center', 'align-right' ],
313 contentsCss: [ '../../../contents.css', 'contents.css' ],
314 height: 600
315 } );
316
317 CKCONSOLE.create( 'widget', { editor: 'editor1' } );
318 CKCONSOLE.create( 'focus', { editor: 'editor1' } );
319 CKCONSOLE.create( 'widget', { editor: 'editor2', folded: true } );
320 CKCONSOLE.create( 'focus', { editor: 'editor2', folded: true } );
321 CKCONSOLE.create( 'widget', { editor: 'editor3' } );
322 CKCONSOLE.create( 'focus', { editor: 'editor3' } );
323 CKCONSOLE.create( 'widget', { editor: 'editor4' } );
324 CKCONSOLE.create( 'focus', { editor: 'editor4' } );
325
326 </script>
327
328 <div id="footer">
329 <hr>
330 <p>
331 CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
332 </p>
333 <p id="copy">
334 Copyright &copy; 2003-2017, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
335 Knabben. All rights reserved.
336 </p>
337 </div>
338</body>
339</html>
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/dialogs/image2.js b/app/assets/javascripts/ckeditor/plugins/image2/dialogs/image2.js new file mode 100644 index 0000000..cb393a3 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/dialogs/image2.js
@@ -0,0 +1,553 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6/**
7 * @fileOverview Image plugin based on Widgets API
8 */
9
10'use strict';
11
12CKEDITOR.dialog.add( 'image2', function( editor ) {
13
14 // RegExp: 123, 123px, empty string ""
15 var regexGetSizeOrEmpty = /(^\s*(\d+)(px)?\s*$)|^$/i,
16
17 lockButtonId = CKEDITOR.tools.getNextId(),
18 resetButtonId = CKEDITOR.tools.getNextId(),
19
20 lang = editor.lang.image2,
21 commonLang = editor.lang.common,
22
23 lockResetStyle = 'margin-top:18px;width:40px;height:20px;',
24 lockResetHtml = new CKEDITOR.template(
25 '<div>' +
26 '<a href="javascript:void(0)" tabindex="-1" title="' + lang.lockRatio + '" class="cke_btn_locked" id="{lockButtonId}" role="checkbox">' +
27 '<span class="cke_icon"></span>' +
28 '<span class="cke_label">' + lang.lockRatio + '</span>' +
29 '</a>' +
30
31 '<a href="javascript:void(0)" tabindex="-1" title="' + lang.resetSize + '" class="cke_btn_reset" id="{resetButtonId}" role="button">' +
32 '<span class="cke_label">' + lang.resetSize + '</span>' +
33 '</a>' +
34 '</div>' ).output( {
35 lockButtonId: lockButtonId,
36 resetButtonId: resetButtonId
37 } ),
38
39 helpers = CKEDITOR.plugins.image2,
40
41 // Editor instance configuration.
42 config = editor.config,
43
44 hasFileBrowser = !!( config.filebrowserImageBrowseUrl || config.filebrowserBrowseUrl ),
45
46 // Content restrictions defined by the widget which
47 // impact on dialog structure and presence of fields.
48 features = editor.widgets.registered.image.features,
49
50 // Functions inherited from image2 plugin.
51 getNatural = helpers.getNatural,
52
53 // Global variables referring to the dialog's context.
54 doc, widget, image,
55
56 // Global variable referring to this dialog's image pre-loader.
57 preLoader,
58
59 // Global variables holding the original size of the image.
60 domWidth, domHeight,
61
62 // Global variables related to image pre-loading.
63 preLoadedWidth, preLoadedHeight, srcChanged,
64
65 // Global variables related to size locking.
66 lockRatio, userDefinedLock,
67
68 // Global variables referring to dialog fields and elements.
69 lockButton, resetButton, widthField, heightField,
70
71 natural;
72
73 // Validates dimension. Allowed values are:
74 // "123px", "123", "" (empty string)
75 function validateDimension() {
76 var match = this.getValue().match( regexGetSizeOrEmpty ),
77 isValid = !!( match && parseInt( match[ 1 ], 10 ) !== 0 );
78
79 if ( !isValid )
80 alert( commonLang[ 'invalid' + CKEDITOR.tools.capitalize( this.id ) ] ); // jshint ignore:line
81
82 return isValid;
83 }
84
85 // Creates a function that pre-loads images. The callback function passes
86 // [image, width, height] or null if loading failed.
87 //
88 // @returns {Function}
89 function createPreLoader() {
90 var image = doc.createElement( 'img' ),
91 listeners = [];
92
93 function addListener( event, callback ) {
94 listeners.push( image.once( event, function( evt ) {
95 removeListeners();
96 callback( evt );
97 } ) );
98 }
99
100 function removeListeners() {
101 var l;
102
103 while ( ( l = listeners.pop() ) )
104 l.removeListener();
105 }
106
107 // @param {String} src.
108 // @param {Function} callback.
109 return function( src, callback, scope ) {
110 addListener( 'load', function() {
111 // Don't use image.$.(width|height) since it's buggy in IE9-10 (http://dev.ckeditor.com/ticket/11159)
112 var dimensions = getNatural( image );
113
114 callback.call( scope, image, dimensions.width, dimensions.height );
115 } );
116
117 addListener( 'error', function() {
118 callback( null );
119 } );
120
121 addListener( 'abort', function() {
122 callback( null );
123 } );
124
125 image.setAttribute( 'src',
126 ( config.baseHref || '' ) + src + '?' + Math.random().toString( 16 ).substring( 2 ) );
127 };
128 }
129
130 // This function updates width and height fields once the
131 // "src" field is altered. Along with dimensions, also the
132 // dimensions lock is adjusted.
133 function onChangeSrc() {
134 var value = this.getValue();
135
136 toggleDimensions( false );
137
138 // Remember that src is different than default.
139 if ( value !== widget.data.src ) {
140 // Update dimensions of the image once it's preloaded.
141 preLoader( value, function( image, width, height ) {
142 // Re-enable width and height fields.
143 toggleDimensions( true );
144
145 // There was problem loading the image. Unlock ratio.
146 if ( !image )
147 return toggleLockRatio( false );
148
149 // Fill width field with the width of the new image.
150 widthField.setValue( editor.config.image2_prefillDimensions === false ? 0 : width );
151
152 // Fill height field with the height of the new image.
153 heightField.setValue( editor.config.image2_prefillDimensions === false ? 0 : height );
154
155 // Cache the new width.
156 preLoadedWidth = width;
157
158 // Cache the new height.
159 preLoadedHeight = height;
160
161 // Check for new lock value if image exist.
162 toggleLockRatio( helpers.checkHasNaturalRatio( image ) );
163 } );
164
165 srcChanged = true;
166 }
167
168 // Value is the same as in widget data but is was
169 // modified back in time. Roll back dimensions when restoring
170 // default src.
171 else if ( srcChanged ) {
172 // Re-enable width and height fields.
173 toggleDimensions( true );
174
175 // Restore width field with cached width.
176 widthField.setValue( domWidth );
177
178 // Restore height field with cached height.
179 heightField.setValue( domHeight );
180
181 // Src equals default one back again.
182 srcChanged = false;
183 }
184
185 // Value is the same as in widget data and it hadn't
186 // been modified.
187 else {
188 // Re-enable width and height fields.
189 toggleDimensions( true );
190 }
191 }
192
193 function onChangeDimension() {
194 // If ratio is un-locked, then we don't care what's next.
195 if ( !lockRatio )
196 return;
197
198 var value = this.getValue();
199
200 // No reason to auto-scale or unlock if the field is empty.
201 if ( !value )
202 return;
203
204 // If the value of the field is invalid (e.g. with %), unlock ratio.
205 if ( !value.match( regexGetSizeOrEmpty ) )
206 toggleLockRatio( false );
207
208 // No automatic re-scale when dimension is '0'.
209 if ( value === '0' )
210 return;
211
212 var isWidth = this.id == 'width',
213 // If dialog opened for the new image, domWidth and domHeight
214 // will be empty. Use dimensions from pre-loader in such case instead.
215 width = domWidth || preLoadedWidth,
216 height = domHeight || preLoadedHeight;
217
218 // If changing width, then auto-scale height.
219 if ( isWidth )
220 value = Math.round( height * ( value / width ) );
221
222 // If changing height, then auto-scale width.
223 else
224 value = Math.round( width * ( value / height ) );
225
226 // If the value is a number, apply it to the other field.
227 if ( !isNaN( value ) )
228 ( isWidth ? heightField : widthField ).setValue( value );
229 }
230
231 // Set-up function for lock and reset buttons:
232 // * Adds lock and reset buttons to focusables. Check if button exist first
233 // because it may be disabled e.g. due to ACF restrictions.
234 // * Register mouseover and mouseout event listeners for UI manipulations.
235 // * Register click event listeners for buttons.
236 function onLoadLockReset() {
237 var dialog = this.getDialog();
238
239 function setupMouseClasses( el ) {
240 el.on( 'mouseover', function() {
241 this.addClass( 'cke_btn_over' );
242 }, el );
243
244 el.on( 'mouseout', function() {
245 this.removeClass( 'cke_btn_over' );
246 }, el );
247 }
248
249 // Create references to lock and reset buttons for this dialog instance.
250 lockButton = doc.getById( lockButtonId );
251 resetButton = doc.getById( resetButtonId );
252
253 // Activate (Un)LockRatio button
254 if ( lockButton ) {
255 // Consider that there's an additional focusable field
256 // in the dialog when the "browse" button is visible.
257 dialog.addFocusable( lockButton, 4 + hasFileBrowser );
258
259 lockButton.on( 'click', function( evt ) {
260 toggleLockRatio();
261 evt.data && evt.data.preventDefault();
262 }, this.getDialog() );
263
264 setupMouseClasses( lockButton );
265 }
266
267 // Activate the reset size button.
268 if ( resetButton ) {
269 // Consider that there's an additional focusable field
270 // in the dialog when the "browse" button is visible.
271 dialog.addFocusable( resetButton, 5 + hasFileBrowser );
272
273 // Fills width and height fields with the original dimensions of the
274 // image (stored in widget#data since widget#init).
275 resetButton.on( 'click', function( evt ) {
276 // If there's a new image loaded, reset button should revert
277 // cached dimensions of pre-loaded DOM element.
278 if ( srcChanged ) {
279 widthField.setValue( preLoadedWidth );
280 heightField.setValue( preLoadedHeight );
281 }
282
283 // If the old image remains, reset button should revert
284 // dimensions as loaded when the dialog was first shown.
285 else {
286 widthField.setValue( domWidth );
287 heightField.setValue( domHeight );
288 }
289
290 evt.data && evt.data.preventDefault();
291 }, this );
292
293 setupMouseClasses( resetButton );
294 }
295 }
296
297 function toggleLockRatio( enable ) {
298 // No locking if there's no radio (i.e. due to ACF).
299 if ( !lockButton )
300 return;
301
302 if ( typeof enable == 'boolean' ) {
303 // If user explicitly wants to decide whether
304 // to lock or not, don't do anything.
305 if ( userDefinedLock )
306 return;
307
308 lockRatio = enable;
309 }
310
311 // Undefined. User changed lock value.
312 else {
313 var width = widthField.getValue(),
314 height;
315
316 userDefinedLock = true;
317 lockRatio = !lockRatio;
318
319 // Automatically adjust height to width to match
320 // the original ratio (based on dom- dimensions).
321 if ( lockRatio && width ) {
322 height = domHeight / domWidth * width;
323
324 if ( !isNaN( height ) )
325 heightField.setValue( Math.round( height ) );
326 }
327 }
328
329 lockButton[ lockRatio ? 'removeClass' : 'addClass' ]( 'cke_btn_unlocked' );
330 lockButton.setAttribute( 'aria-checked', lockRatio );
331
332 // Ratio button hc presentation - WHITE SQUARE / BLACK SQUARE
333 if ( CKEDITOR.env.hc ) {
334 var icon = lockButton.getChild( 0 );
335 icon.setHtml( lockRatio ? CKEDITOR.env.ie ? '\u25A0' : '\u25A3' : CKEDITOR.env.ie ? '\u25A1' : '\u25A2' );
336 }
337 }
338
339 function toggleDimensions( enable ) {
340 var method = enable ? 'enable' : 'disable';
341
342 widthField[ method ]();
343 heightField[ method ]();
344 }
345
346 var srcBoxChildren = [
347 {
348 id: 'src',
349 type: 'text',
350 label: commonLang.url,
351 onKeyup: onChangeSrc,
352 onChange: onChangeSrc,
353 setup: function( widget ) {
354 this.setValue( widget.data.src );
355 },
356 commit: function( widget ) {
357 widget.setData( 'src', this.getValue() );
358 },
359 validate: CKEDITOR.dialog.validate.notEmpty( lang.urlMissing )
360 }
361 ];
362
363 // Render the "Browse" button on demand to avoid an "empty" (hidden child)
364 // space in dialog layout that distorts the UI.
365 if ( hasFileBrowser ) {
366 srcBoxChildren.push( {
367 type: 'button',
368 id: 'browse',
369 // v-align with the 'txtUrl' field.
370 // TODO: We need something better than a fixed size here.
371 style: 'display:inline-block;margin-top:14px;',
372 align: 'center',
373 label: editor.lang.common.browseServer,
374 hidden: true,
375 filebrowser: 'info:src'
376 } );
377 }
378
379 return {
380 title: lang.title,
381 minWidth: 250,
382 minHeight: 100,
383 onLoad: function() {
384 // Create a "global" reference to the document for this dialog instance.
385 doc = this._.element.getDocument();
386
387 // Create a pre-loader used for determining dimensions of new images.
388 preLoader = createPreLoader();
389 },
390 onShow: function() {
391 // Create a "global" reference to edited widget.
392 widget = this.widget;
393
394 // Create a "global" reference to widget's image.
395 image = widget.parts.image;
396
397 // Reset global variables.
398 srcChanged = userDefinedLock = lockRatio = false;
399
400 // Natural dimensions of the image.
401 natural = getNatural( image );
402
403 // Get the natural width of the image.
404 preLoadedWidth = domWidth = natural.width;
405
406 // Get the natural height of the image.
407 preLoadedHeight = domHeight = natural.height;
408 },
409 contents: [
410 {
411 id: 'info',
412 label: lang.infoTab,
413 elements: [
414 {
415 type: 'vbox',
416 padding: 0,
417 children: [
418 {
419 type: 'hbox',
420 widths: [ '100%' ],
421 className: 'cke_dialog_image_url',
422 children: srcBoxChildren
423 }
424 ]
425 },
426 {
427 id: 'alt',
428 type: 'text',
429 label: lang.alt,
430 setup: function( widget ) {
431 this.setValue( widget.data.alt );
432 },
433 commit: function( widget ) {
434 widget.setData( 'alt', this.getValue() );
435 },
436 validate: editor.config.image2_altRequired === true ? CKEDITOR.dialog.validate.notEmpty( lang.altMissing ) : null
437 },
438 {
439 type: 'hbox',
440 widths: [ '25%', '25%', '50%' ],
441 requiredContent: features.dimension.requiredContent,
442 children: [
443 {
444 type: 'text',
445 width: '45px',
446 id: 'width',
447 label: commonLang.width,
448 validate: validateDimension,
449 onKeyUp: onChangeDimension,
450 onLoad: function() {
451 widthField = this;
452 },
453 setup: function( widget ) {
454 this.setValue( widget.data.width );
455 },
456 commit: function( widget ) {
457 widget.setData( 'width', this.getValue() );
458 }
459 },
460 {
461 type: 'text',
462 id: 'height',
463 width: '45px',
464 label: commonLang.height,
465 validate: validateDimension,
466 onKeyUp: onChangeDimension,
467 onLoad: function() {
468 heightField = this;
469 },
470 setup: function( widget ) {
471 this.setValue( widget.data.height );
472 },
473 commit: function( widget ) {
474 widget.setData( 'height', this.getValue() );
475 }
476 },
477 {
478 id: 'lock',
479 type: 'html',
480 style: lockResetStyle,
481 onLoad: onLoadLockReset,
482 setup: function( widget ) {
483 toggleLockRatio( widget.data.lock );
484 },
485 commit: function( widget ) {
486 widget.setData( 'lock', lockRatio );
487 },
488 html: lockResetHtml
489 }
490 ]
491 },
492 {
493 type: 'hbox',
494 id: 'alignment',
495 requiredContent: features.align.requiredContent,
496 children: [
497 {
498 id: 'align',
499 type: 'radio',
500 items: [
501 [ commonLang.alignNone, 'none' ],
502 [ commonLang.alignLeft, 'left' ],
503 [ commonLang.alignCenter, 'center' ],
504 [ commonLang.alignRight, 'right' ]
505 ],
506 label: commonLang.align,
507 setup: function( widget ) {
508 this.setValue( widget.data.align );
509 },
510 commit: function( widget ) {
511 widget.setData( 'align', this.getValue() );
512 }
513 }
514 ]
515 },
516 {
517 id: 'hasCaption',
518 type: 'checkbox',
519 label: lang.captioned,
520 requiredContent: features.caption.requiredContent,
521 setup: function( widget ) {
522 this.setValue( widget.data.hasCaption );
523 },
524 commit: function( widget ) {
525 widget.setData( 'hasCaption', this.getValue() );
526 }
527 }
528 ]
529 },
530 {
531 id: 'Upload',
532 hidden: true,
533 filebrowser: 'uploadButton',
534 label: lang.uploadTab,
535 elements: [
536 {
537 type: 'file',
538 id: 'upload',
539 label: lang.btnUpload,
540 style: 'height:40px'
541 },
542 {
543 type: 'fileButton',
544 id: 'uploadButton',
545 filebrowser: 'info:src',
546 label: lang.btnUpload,
547 'for': [ 'Upload', 'upload' ]
548 }
549 ]
550 }
551 ]
552 };
553} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/icons/hidpi/image.png b/app/assets/javascripts/ckeditor/plugins/image2/icons/hidpi/image.png new file mode 100644 index 0000000..d0f21ae --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/icons/hidpi/image.png
Binary files differ
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/icons/image.png b/app/assets/javascripts/ckeditor/plugins/image2/icons/image.png new file mode 100644 index 0000000..8ea9725 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/icons/image.png
Binary files differ
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/af.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/af.js new file mode 100644 index 0000000..be02258 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/af.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'af', {
6 alt: 'Alternatiewe teks',
7 btnUpload: 'Stuur na bediener',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Afbeelding informasie',
11 lockRatio: 'Vaste proporsie',
12 menu: 'Afbeelding eienskappe',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Herstel grootte',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Afbeelding eienskappe',
18 uploadTab: 'Oplaai',
19 urlMissing: 'Die URL na die afbeelding ontbreek.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ar.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ar.js new file mode 100644 index 0000000..729ba5e --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ar.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ar', {
6 alt: 'عنوان الصورة',
7 btnUpload: 'أرسلها للخادم',
8 captioned: 'صورة ذات اسم',
9 captionPlaceholder: 'تسمية',
10 infoTab: 'معلومات الصورة',
11 lockRatio: 'تناسق الحجم',
12 menu: 'خصائص الصورة',
13 pathName: 'صورة',
14 pathNameCaption: 'تسمية',
15 resetSize: 'إستعادة الحجم الأصلي',
16 resizer: 'انقر ثم اسحب للتحجيم',
17 title: 'خصائص الصورة',
18 uploadTab: 'رفع',
19 urlMissing: 'عنوان مصدر الصورة مفقود',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/az.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/az.js new file mode 100644 index 0000000..8422f43 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/az.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'az', {
6 alt: 'Alternativ mətn',
7 btnUpload: 'Serverə göndər',
8 captioned: 'Altyazı olan şəkil',
9 captionPlaceholder: 'Altyazı',
10 infoTab: 'Şəkil haqqında məlumat',
11 lockRatio: 'Ölçülərin nisbəti saxla',
12 menu: 'Şəklin seçimləri',
13 pathName: 'Şəkil',
14 pathNameCaption: 'Altyazı',
15 resetSize: 'Ölçüləri qaytar',
16 resizer: 'Ölçülər dəyişmək üçün tıklayın və aparın',
17 title: 'Şəklin seçimləri',
18 uploadTab: 'Serverə yüklə',
19 urlMissing: 'Şəklin ünvanı yanlışdır.',
20 altMissing: 'Alternativ mətn tapılmayıb'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/bg.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/bg.js new file mode 100644 index 0000000..66cec57 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/bg.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'bg', {
6 alt: 'Алтернативен текст',
7 btnUpload: 'Изпрати я на сървъра',
8 captioned: 'Надписано изображение',
9 captionPlaceholder: 'Надпис',
10 infoTab: 'Детайли за изображението',
11 lockRatio: 'Заключване на съотношението',
12 menu: 'Настройки на изображението',
13 pathName: 'изображение',
14 pathNameCaption: 'надпис',
15 resetSize: 'Нулиране на размер',
16 resizer: 'Кликни и влачи, за да преоразмериш',
17 title: 'Настройки на изображението',
18 uploadTab: 'Качване',
19 urlMissing: 'URL адреса на изображението липсва.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/bn.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/bn.js new file mode 100644 index 0000000..cc057b6 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/bn.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'bn', {
6 alt: 'বিকল্প টেক্সট',
7 btnUpload: 'ইহাকে সার্ভারে প্রেরন কর',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'ছবির তথ্য',
11 lockRatio: 'অনুপাত লক কর',
12 menu: 'ছবির প্রোপার্টি',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'সাইজ পূর্বাবস্থায় ফিরিয়ে দাও',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'ছবির প্রোপার্টি',
18 uploadTab: 'আপলোড',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/bs.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/bs.js new file mode 100644 index 0000000..d0c57c8 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/bs.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'bs', {
6 alt: 'Tekst na slici',
7 btnUpload: 'Šalji na server',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Info slike',
11 lockRatio: 'Zakljuèaj odnos',
12 menu: 'Svojstva slike',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Resetuj dimenzije',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Svojstva slike',
18 uploadTab: 'Šalji',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ca.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ca.js new file mode 100644 index 0000000..66feadc --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ca.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ca', {
6 alt: 'Text alternatiu',
7 btnUpload: 'Envia-la al servidor',
8 captioned: 'Imatge amb subtítol',
9 captionPlaceholder: 'Títol',
10 infoTab: 'Informació de la imatge',
11 lockRatio: 'Bloqueja les proporcions',
12 menu: 'Propietats de la imatge',
13 pathName: 'imatge',
14 pathNameCaption: 'subtítol',
15 resetSize: 'Restaura la mida',
16 resizer: 'Clicar i arrossegar per redimensionar',
17 title: 'Propietats de la imatge',
18 uploadTab: 'Puja',
19 urlMissing: 'Falta la URL de la imatge.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/cs.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/cs.js new file mode 100644 index 0000000..c50394e --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/cs.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'cs', {
6 alt: 'Alternativní text',
7 btnUpload: 'Odeslat na server',
8 captioned: 'Obrázek s popisem',
9 captionPlaceholder: 'Popis',
10 infoTab: 'Informace o obrázku',
11 lockRatio: 'Zámek',
12 menu: 'Vlastnosti obrázku',
13 pathName: 'Obrázek',
14 pathNameCaption: 'Popis',
15 resetSize: 'Původní velikost',
16 resizer: 'Klepněte a táhněte pro změnu velikosti',
17 title: 'Vlastnosti obrázku',
18 uploadTab: 'Odeslat',
19 urlMissing: 'Zadané URL zdroje obrázku nebylo nalezeno.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/cy.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/cy.js new file mode 100644 index 0000000..7051680 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/cy.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'cy', {
6 alt: 'Testun Amgen',
7 btnUpload: 'Anfon i\'r Gweinydd',
8 captioned: 'Delwedd â phennawd',
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Gwyb Delwedd',
11 lockRatio: 'Cloi Cymhareb',
12 menu: 'Priodweddau Delwedd',
13 pathName: 'delwedd',
14 pathNameCaption: 'pennawd',
15 resetSize: 'Ailosod Maint',
16 resizer: 'Clicio a llusgo i ail-meintio',
17 title: 'Priodweddau Delwedd',
18 uploadTab: 'Lanlwytho',
19 urlMissing: 'URL gwreiddiol y ddelwedd ar goll.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/da.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/da.js new file mode 100644 index 0000000..3d7c443 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/da.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'da', {
6 alt: 'Alternativ tekst',
7 btnUpload: 'Upload fil til serveren',
8 captioned: 'Tekstet billede',
9 captionPlaceholder: 'Tekst',
10 infoTab: 'Generelt',
11 lockRatio: 'Lås størrelsesforhold',
12 menu: 'Egenskaber for billede',
13 pathName: 'billede',
14 pathNameCaption: 'tekst',
15 resetSize: 'Nulstil størrelse',
16 resizer: 'Klik og træk for at ændre størrelsen',
17 title: 'Egenskaber for billede',
18 uploadTab: 'Upload',
19 urlMissing: 'Kilde på billed-URL mangler',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/de-ch.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/de-ch.js new file mode 100644 index 0000000..ec55d1f --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/de-ch.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'de-ch', {
6 alt: 'Alternativer Text',
7 btnUpload: 'Zum Server senden',
8 captioned: 'Bild mit Überschrift',
9 captionPlaceholder: 'Überschrift',
10 infoTab: 'Bildinfo',
11 lockRatio: 'Größenverhältnis beibehalten',
12 menu: 'Bildeigenschaften',
13 pathName: 'Bild',
14 pathNameCaption: 'Überschrift',
15 resetSize: 'Grösse zurücksetzen',
16 resizer: 'Zum Vergrössern auswählen und ziehen',
17 title: 'Bild-Eigenschaften',
18 uploadTab: 'Hochladen',
19 urlMissing: 'Bildquellen-URL fehlt.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/de.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/de.js new file mode 100644 index 0000000..0b5b6f6 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/de.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'de', {
6 alt: 'Alternativer Text',
7 btnUpload: 'Zum Server senden',
8 captioned: 'Bild mit Überschrift',
9 captionPlaceholder: 'Überschrift',
10 infoTab: 'Bildinfo',
11 lockRatio: 'Größenverhältnis beibehalten',
12 menu: 'Bildeigenschaften',
13 pathName: 'Bild',
14 pathNameCaption: 'Überschrift',
15 resetSize: 'Größe zurücksetzen',
16 resizer: 'Zum Vergrößern auswählen und ziehen',
17 title: 'Bild-Eigenschaften',
18 uploadTab: 'Hochladen',
19 urlMissing: 'Bildquellen-URL fehlt.',
20 altMissing: 'Alternativer Text fehlt.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/el.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/el.js new file mode 100644 index 0000000..8167636 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/el.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'el', {
6 alt: 'Εναλλακτικό Κείμενο',
7 btnUpload: 'Αποστολή στον Διακομιστή',
8 captioned: 'Εικόνα με λεζάντα',
9 captionPlaceholder: 'Λεζάντα',
10 infoTab: 'Πληροφορίες Εικόνας',
11 lockRatio: 'Κλείδωμα Αναλογίας',
12 menu: 'Ιδιότητες Εικόνας',
13 pathName: 'εικόνα',
14 pathNameCaption: 'λεζάντα',
15 resetSize: 'Επαναφορά Αρχικού Μεγέθους',
16 resizer: 'Κάνετε κλικ και σύρετε το ποντίκι για να αλλάξετε το μέγεθος',
17 title: 'Ιδιότητες Εικόνας',
18 uploadTab: 'Αποστολή',
19 urlMissing: 'Λείπει το πηγαίο URL της εικόνας.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/en-au.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/en-au.js new file mode 100644 index 0000000..f51ffa3 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/en-au.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'en-au', {
6 alt: 'Alternative Text',
7 btnUpload: 'Send it to the Server',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Image Info',
11 lockRatio: 'Lock Ratio',
12 menu: 'Image Properties',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Reset Size',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Image Properties',
18 uploadTab: 'Upload',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/en-ca.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/en-ca.js new file mode 100644 index 0000000..79dabcf --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/en-ca.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'en-ca', {
6 alt: 'Alternative Text',
7 btnUpload: 'Send it to the Server',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Image Info',
11 lockRatio: 'Lock Ratio',
12 menu: 'Image Properties',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Reset Size',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Image Properties',
18 uploadTab: 'Upload',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/en-gb.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/en-gb.js new file mode 100644 index 0000000..99c4f80 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/en-gb.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'en-gb', {
6 alt: 'Alternative Text',
7 btnUpload: 'Send it to the Server',
8 captioned: 'Captioned image',
9 captionPlaceholder: 'Caption',
10 infoTab: 'Image Info',
11 lockRatio: 'Lock Ratio',
12 menu: 'Image Properties',
13 pathName: 'image',
14 pathNameCaption: 'caption',
15 resetSize: 'Reset Size',
16 resizer: 'Click and drag to resize',
17 title: 'Image Properties',
18 uploadTab: 'Upload',
19 urlMissing: 'Image source URL is missing.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/en.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/en.js new file mode 100644 index 0000000..61abe6f --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/en.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'en', {
6 alt: 'Alternative Text',
7 btnUpload: 'Send it to the Server',
8 captioned: 'Captioned image',
9 captionPlaceholder: 'Caption',
10 infoTab: 'Image Info',
11 lockRatio: 'Lock Ratio',
12 menu: 'Image Properties',
13 pathName: 'image',
14 pathNameCaption: 'caption',
15 resetSize: 'Reset Size',
16 resizer: 'Click and drag to resize',
17 title: 'Image Properties',
18 uploadTab: 'Upload',
19 urlMissing: 'Image source URL is missing.',
20 altMissing: 'Alternative text is missing.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/eo.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/eo.js new file mode 100644 index 0000000..604b91a --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/eo.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'eo', {
6 alt: 'Anstataŭiga Teksto',
7 btnUpload: 'Sendu al Servilo',
8 captioned: 'Bildo kun apudskribo',
9 captionPlaceholder: 'Apudskribo',
10 infoTab: 'Informoj pri Bildo',
11 lockRatio: 'Konservi Proporcion',
12 menu: 'Atributoj de Bildo',
13 pathName: 'bildo',
14 pathNameCaption: 'apudskribo',
15 resetSize: 'Origina Grando',
16 resizer: 'Kliki kaj treni por ŝanĝi la grandon',
17 title: 'Atributoj de Bildo',
18 uploadTab: 'Alŝuti',
19 urlMissing: 'La fontretadreso de la bildo mankas.',
20 altMissing: 'Alternativa teksto mankas.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/es-mx.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/es-mx.js new file mode 100644 index 0000000..7005bd2 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/es-mx.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'es-mx', {
6 alt: 'Texto alternativo',
7 btnUpload: 'Enviar al servidor',
8 captioned: 'Imagen subtitulada',
9 captionPlaceholder: 'Subtítulo',
10 infoTab: 'Información de la imagen',
11 lockRatio: 'Bloquear aspecto',
12 menu: 'Propiedades de la imagen',
13 pathName: 'imagen',
14 pathNameCaption: 'subtítulo',
15 resetSize: 'Reiniciar tamaño',
16 resizer: 'Presiona y arrastra para redimensionar',
17 title: 'Propiedades de imagen',
18 uploadTab: 'Cargar',
19 urlMissing: 'Falta la URL de origen de la imagen.',
20 altMissing: 'Falta texto alternativo.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/es.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/es.js new file mode 100644 index 0000000..9a02c33 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/es.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'es', {
6 alt: 'Texto Alternativo',
7 btnUpload: 'Enviar al Servidor',
8 captioned: 'Imagen subtitulada',
9 captionPlaceholder: 'Leyenda',
10 infoTab: 'Información de Imagen',
11 lockRatio: 'Proporcional',
12 menu: 'Propiedades de Imagen',
13 pathName: 'image',
14 pathNameCaption: 'subtítulo',
15 resetSize: 'Tamaño Original',
16 resizer: 'Dar clic y arrastrar para cambiar tamaño',
17 title: 'Propiedades de Imagen',
18 uploadTab: 'Cargar',
19 urlMissing: 'Debe indicar la URL de la imagen.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/et.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/et.js new file mode 100644 index 0000000..02ee064 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/et.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'et', {
6 alt: 'Alternatiivne tekst',
7 btnUpload: 'Saada serverisse',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Pildi info',
11 lockRatio: 'Lukusta kuvasuhe',
12 menu: 'Pildi omadused',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Lähtesta suurus',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Pildi omadused',
18 uploadTab: 'Lae üles',
19 urlMissing: 'Pildi lähte-URL on puudu.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/eu.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/eu.js new file mode 100644 index 0000000..9ac8268 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/eu.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'eu', {
6 alt: 'Ordezko testua',
7 btnUpload: 'Bidali zerbitzarira',
8 captioned: 'Argazki oina',
9 captionPlaceholder: 'Argazki oina',
10 infoTab: 'Irudiaren informazioa',
11 lockRatio: 'Blokeatu erlazioa',
12 menu: 'Irudiaren propietateak',
13 pathName: 'Irudia',
14 pathNameCaption: 'Argazki oina',
15 resetSize: 'Berrezarri tamaina',
16 resizer: 'Klikatu eta arrastatu tamainaz aldatzeko',
17 title: 'Irudiaren propietateak',
18 uploadTab: 'Kargatu',
19 urlMissing: 'Irudiaren iturburuaren URLa falta da.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/fa.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/fa.js new file mode 100644 index 0000000..23db9ed --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/fa.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'fa', {
6 alt: 'متن جایگزین',
7 btnUpload: 'به سرور بفرست',
8 captioned: 'تصویر زیرنویس شده',
9 captionPlaceholder: 'عنوان',
10 infoTab: 'اطلاعات تصویر',
11 lockRatio: 'قفل کردن نسبت',
12 menu: 'ویژگی​های تصویر',
13 pathName: 'تصویر',
14 pathNameCaption: 'عنوان',
15 resetSize: 'بازنشانی اندازه',
16 resizer: 'کلیک و کشیدن برای تغییر اندازه',
17 title: 'ویژگی​های تصویر',
18 uploadTab: 'بالاگذاری',
19 urlMissing: 'آدرس URL اصلی تصویر یافت نشد.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/fi.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/fi.js new file mode 100644 index 0000000..cb89f0a --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/fi.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'fi', {
6 alt: 'Vaihtoehtoinen teksti',
7 btnUpload: 'Lähetä palvelimelle',
8 captioned: 'Kuva kuvatekstillä',
9 captionPlaceholder: 'Kuvateksti',
10 infoTab: 'Kuvan tiedot',
11 lockRatio: 'Lukitse suhteet',
12 menu: 'Kuvan ominaisuudet',
13 pathName: 'kuva',
14 pathNameCaption: 'kuvateksti',
15 resetSize: 'Alkuperäinen koko',
16 resizer: 'Klikkaa ja raahaa muuttaaksesi kokoa',
17 title: 'Kuvan ominaisuudet',
18 uploadTab: 'Lisää tiedosto',
19 urlMissing: 'Kuvan lähdeosoite puuttuu.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/fo.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/fo.js new file mode 100644 index 0000000..86c001a --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/fo.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'fo', {
6 alt: 'Alternativur tekstur',
7 btnUpload: 'Send til ambætaran',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Myndaupplýsingar',
11 lockRatio: 'Læs lutfallið',
12 menu: 'Myndaeginleikar',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Upprunastødd',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Myndaeginleikar',
18 uploadTab: 'Send til ambætaran',
19 urlMissing: 'URL til mynd manglar.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/fr-ca.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/fr-ca.js new file mode 100644 index 0000000..38afd99 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/fr-ca.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'fr-ca', {
6 alt: 'Texte alternatif',
7 btnUpload: 'Envoyer sur le serveur',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Informations sur l\'image2',
11 lockRatio: 'Verrouiller les proportions',
12 menu: 'Propriétés de l\'image2',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Taille originale',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Propriétés de l\'image2',
18 uploadTab: 'Téléverser',
19 urlMissing: 'L\'URL de la source de l\'image est manquant.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/fr.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/fr.js new file mode 100644 index 0000000..5c14790 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/fr.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'fr', {
6 alt: 'Texte alternatif',
7 btnUpload: 'Envoyer sur le serveur',
8 captioned: 'Image légendée',
9 captionPlaceholder: 'Légende',
10 infoTab: 'Informations sur l\'image',
11 lockRatio: 'Conserver les proportions',
12 menu: 'Propriétés de l\'image',
13 pathName: 'image',
14 pathNameCaption: 'légende',
15 resetSize: 'Réinitialiser la taille',
16 resizer: 'Cliquer et glisser pour redimensionner',
17 title: 'Propriétés de l\'image',
18 uploadTab: 'Téléverser',
19 urlMissing: 'L\'URL source de l\'image est manquante.',
20 altMissing: 'Vous n\'avez pas indiqué de texte de remplacement.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/gl.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/gl.js new file mode 100644 index 0000000..a2a824c --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/gl.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'gl', {
6 alt: 'Texto alternativo',
7 btnUpload: 'Enviar ao servidor',
8 captioned: 'Imaxe con lenda',
9 captionPlaceholder: 'Lenda',
10 infoTab: 'Información da imaxe',
11 lockRatio: 'Proporcional',
12 menu: 'Propiedades da imaxe',
13 pathName: 'Imaxe',
14 pathNameCaption: 'lenda',
15 resetSize: 'Tamaño orixinal',
16 resizer: 'Prema e arrastre para axustar o tamaño',
17 title: 'Propiedades da imaxe',
18 uploadTab: 'Cargar',
19 urlMissing: 'Non se atopa o URL da imaxe.',
20 altMissing: 'Non foi posíbel atopar o texto alternativo.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/gu.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/gu.js new file mode 100644 index 0000000..25a0cf3 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/gu.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'gu', {
6 alt: 'ઑલ્ટર્નટ ટેક્સ્ટ',
7 btnUpload: 'આ સર્વરને મોકલવું',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'ચિત્ર ની જાણકારી',
11 lockRatio: 'લૉક ગુણોત્તર',
12 menu: 'ચિત્રના ગુણ',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'રીસેટ સાઇઝ',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'ચિત્રના ગુણ',
18 uploadTab: 'અપલોડ',
19 urlMissing: 'ઈમેજની મૂળ URL છે નહી.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/he.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/he.js new file mode 100644 index 0000000..49bef06 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/he.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'he', {
6 alt: 'טקסט חלופי',
7 btnUpload: 'שליחה לשרת',
8 captioned: 'כותרת תמונה',
9 captionPlaceholder: 'כותרת',
10 infoTab: 'מידע על התמונה',
11 lockRatio: 'נעילת היחס',
12 menu: 'תכונות התמונה',
13 pathName: 'תמונה',
14 pathNameCaption: 'כותרת',
15 resetSize: 'איפוס הגודל',
16 resizer: 'לחץ וגרור לשינוי הגודל',
17 title: 'מאפייני התמונה',
18 uploadTab: 'העלאה',
19 urlMissing: 'כתובת התמונה חסרה.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/hi.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/hi.js new file mode 100644 index 0000000..029ff92 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/hi.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'hi', {
6 alt: 'वैकल्पिक टेक्स्ट',
7 btnUpload: 'इसे सर्वर को भेजें',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'तस्वीर की जानकारी',
11 lockRatio: 'लॉक अनुपात',
12 menu: 'तस्वीर प्रॉपर्टीज़',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'रीसॅट साइज़',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'तस्वीर प्रॉपर्टीज़',
18 uploadTab: 'अपलोड',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/hr.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/hr.js new file mode 100644 index 0000000..6d05047 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/hr.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'hr', {
6 alt: 'Alternativni tekst',
7 btnUpload: 'Pošalji na server',
8 captioned: 'Titl slike',
9 captionPlaceholder: 'Titl',
10 infoTab: 'Info slike',
11 lockRatio: 'Zaključaj odnos',
12 menu: 'Svojstva slika',
13 pathName: 'slika',
14 pathNameCaption: 'titl',
15 resetSize: 'Obriši veličinu',
16 resizer: 'Odaberi i povuci za promjenu veličine',
17 title: 'Svojstva slika',
18 uploadTab: 'Pošalji',
19 urlMissing: 'Nedostaje URL slike.',
20 altMissing: 'Nedostaje alternativni tekst.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/hu.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/hu.js new file mode 100644 index 0000000..d1f6484 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/hu.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'hu', {
6 alt: 'Buborék szöveg',
7 btnUpload: 'Küldés a szerverre',
8 captioned: 'Feliratozott kép',
9 captionPlaceholder: 'Képfelirat',
10 infoTab: 'Alaptulajdonságok',
11 lockRatio: 'Arány megtartása',
12 menu: 'Kép tulajdonságai',
13 pathName: 'kép',
14 pathNameCaption: 'felirat',
15 resetSize: 'Eredeti méret',
16 resizer: 'Kattints és húzz az átméretezéshez',
17 title: 'Kép tulajdonságai',
18 uploadTab: 'Feltöltés',
19 urlMissing: 'Hiányzik a kép URL-je',
20 altMissing: 'Az alternatív szöveg hiányzik.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/id.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/id.js new file mode 100644 index 0000000..dd783c4 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/id.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'id', {
6 alt: 'Teks alternatif',
7 btnUpload: 'Kirim ke Server',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Info Gambar',
11 lockRatio: 'Lock Ratio', // MISSING
12 menu: 'Image Properties', // MISSING
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Atur Ulang Ukuran',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Image Properties', // MISSING
18 uploadTab: 'Unggah',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/is.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/is.js new file mode 100644 index 0000000..a4a002d --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/is.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'is', {
6 alt: 'Baklægur texti',
7 btnUpload: 'Hlaða upp',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Almennt',
11 lockRatio: 'Festa stærðarhlutfall',
12 menu: 'Eigindi myndar',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Reikna stærð',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Eigindi myndar',
18 uploadTab: 'Senda upp',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/it.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/it.js new file mode 100644 index 0000000..95ae97a --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/it.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'it', {
6 alt: 'Testo alternativo',
7 btnUpload: 'Invia al server',
8 captioned: 'Immagine con didascalia',
9 captionPlaceholder: 'Didascalia',
10 infoTab: 'Informazioni immagine',
11 lockRatio: 'Blocca rapporto',
12 menu: 'Proprietà immagine',
13 pathName: 'immagine',
14 pathNameCaption: 'didascalia',
15 resetSize: 'Reimposta dimensione',
16 resizer: 'Fare clic e trascinare per ridimensionare',
17 title: 'Proprietà immagine',
18 uploadTab: 'Carica',
19 urlMissing: 'Manca l\'URL dell\'immagine.',
20 altMissing: 'Testo alternativo mancante.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ja.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ja.js new file mode 100644 index 0000000..4cf0ad8 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ja.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ja', {
6 alt: '代替テキスト',
7 btnUpload: 'サーバーに送信',
8 captioned: 'キャプションを付ける',
9 captionPlaceholder: 'キャプション',
10 infoTab: '画像情報',
11 lockRatio: '比率を固定',
12 menu: '画像のプロパティ',
13 pathName: 'image',
14 pathNameCaption: 'caption',
15 resetSize: 'サイズをリセット',
16 resizer: 'ドラッグしてリサイズ',
17 title: '画像のプロパティ',
18 uploadTab: 'アップロード',
19 urlMissing: '画像のURLを入力してください。',
20 altMissing: '代替テキストを入力してください。'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ka.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ka.js new file mode 100644 index 0000000..64e06d0 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ka.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ka', {
6 alt: 'სანაცვლო ტექსტი',
7 btnUpload: 'სერვერისთვის გაგზავნა',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'სურათის ინფორმცია',
11 lockRatio: 'პროპორციის შენარჩუნება',
12 menu: 'სურათის პარამეტრები',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'ზომის დაბრუნება',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'სურათის პარამეტრები',
18 uploadTab: 'აქაჩვა',
19 urlMissing: 'სურათის URL არაა შევსებული.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/km.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/km.js new file mode 100644 index 0000000..ea193ed --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/km.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'km', {
6 alt: 'អត្ថបទជំនួស',
7 btnUpload: 'បញ្ជូនទៅកាន់ម៉ាស៊ីនផ្តល់សេវា',
8 captioned: 'រូប​ដែល​មាន​ចំណង​ជើង',
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'ពត៌មានអំពីរូបភាព',
11 lockRatio: 'ចាក់​សោ​ផល​ធៀប',
12 menu: 'លក្ខណៈ​សម្បត្តិ​រូប​ភាព',
13 pathName: 'រូបភាព',
14 pathNameCaption: 'ចំណងជើង',
15 resetSize: 'កំណត់ទំហំឡើងវិញ',
16 resizer: 'ចុច​ហើយ​ទាញ​ដើម្បី​ប្ដូរ​ទំហំ',
17 title: 'លក្ខណៈ​សម្បត្តិ​រូប​ភាប',
18 uploadTab: 'ផ្ទុក​ឡើង',
19 urlMissing: 'ខ្វះ URL ប្រភព​រូប​ភាព។',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ko.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ko.js new file mode 100644 index 0000000..85d843d --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ko.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ko', {
6 alt: '대체 문자열',
7 btnUpload: '서버로 전송',
8 captioned: '이미지 설명 넣기',
9 captionPlaceholder: '설명',
10 infoTab: '이미지 정보',
11 lockRatio: '비율 유지',
12 menu: '이미지 속성',
13 pathName: '이미지',
14 pathNameCaption: '설명',
15 resetSize: '원래 크기로',
16 resizer: '크기를 조절하려면 클릭 후 드래그 하세요',
17 title: '이미지 속성',
18 uploadTab: '업로드',
19 urlMissing: '이미지 원본 주소(URL)가 없습니다.',
20 altMissing: '대체 문자가 없습니다.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ku.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ku.js new file mode 100644 index 0000000..c8b06a1 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ku.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ku', {
6 alt: 'جێگرەوەی دەق',
7 btnUpload: 'ناردنی بۆ ڕاژه',
8 captioned: 'وێنەی بەسەردێر',
9 captionPlaceholder: 'سەردێر',
10 infoTab: 'زانیاری وێنه',
11 lockRatio: 'داخستنی ڕێژه',
12 menu: 'خاسیەتی وێنه',
13 pathName: 'وێنە',
14 pathNameCaption: 'سەردێر',
15 resetSize: 'ڕێکخستنەوەی قەباره',
16 resizer: 'کرتەبکە و ڕایبکێشە بۆ قەبارە گۆڕین',
17 title: 'خاسیەتی وێنه',
18 uploadTab: 'بارکردن',
19 urlMissing: 'سەرچاوەی بەستەری وێنه بزره',
20 altMissing: 'جێگرەوەی دەق لەدەست چووە.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/lt.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/lt.js new file mode 100644 index 0000000..73514ee --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/lt.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'lt', {
6 alt: 'Alternatyvus Tekstas',
7 btnUpload: 'Siųsti į serverį',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Vaizdo informacija',
11 lockRatio: 'Išlaikyti proporciją',
12 menu: 'Vaizdo savybės',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Atstatyti dydį',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Vaizdo savybės',
18 uploadTab: 'Siųsti',
19 urlMissing: 'Paveiksliuko nuorodos nėra.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/lv.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/lv.js new file mode 100644 index 0000000..aa5f8d3 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/lv.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'lv', {
6 alt: 'Alternatīvais teksts',
7 btnUpload: 'Nosūtīt serverim',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Informācija par attēlu',
11 lockRatio: 'Nemainīga Augstuma/Platuma attiecība',
12 menu: 'Attēla īpašības',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Atjaunot sākotnējo izmēru',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Attēla īpašības',
18 uploadTab: 'Augšupielādēt',
19 urlMissing: 'Trūkst attēla atrašanās adrese.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/mk.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/mk.js new file mode 100644 index 0000000..8edd69a --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/mk.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'mk', {
6 alt: 'Алтернативен текст',
7 btnUpload: 'Прикачи на сервер',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Информации за сликата',
11 lockRatio: 'Зачувај пропорција',
12 menu: 'Својства на сликата',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Ресетирај големина',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Својства на сликата',
18 uploadTab: 'Прикачи',
19 urlMissing: 'Недостасува URL-то на сликата.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/mn.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/mn.js new file mode 100644 index 0000000..e985509 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/mn.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'mn', {
6 alt: 'Зургийг орлох бичвэр',
7 btnUpload: 'Үүнийг сервэррүү илгээ',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Зурагны мэдээлэл',
11 lockRatio: 'Радио түгжих',
12 menu: 'Зураг',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'хэмжээ дахин оноох',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Зураг',
18 uploadTab: 'Илгээж ачаалах',
19 urlMissing: 'Зургийн эх сурвалжийн хаяг (URL) байхгүй байна.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ms.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ms.js new file mode 100644 index 0000000..50d73d9 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ms.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ms', {
6 alt: 'Text Alternatif',
7 btnUpload: 'Hantar ke Server',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Info Imej',
11 lockRatio: 'Tetapkan Nisbah',
12 menu: 'Ciri-ciri Imej',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Saiz Set Semula',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Ciri-ciri Imej',
18 uploadTab: 'Muat Naik',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/nb.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/nb.js new file mode 100644 index 0000000..dce7f1e --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/nb.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'nb', {
6 alt: 'Alternativ tekst',
7 btnUpload: 'Send det til serveren',
8 captioned: 'Bilde med bildetekst',
9 captionPlaceholder: 'Bildetekst',
10 infoTab: 'Bildeinformasjon',
11 lockRatio: 'Lås forhold',
12 menu: 'Bildeegenskaper',
13 pathName: 'bilde',
14 pathNameCaption: 'bildetekst',
15 resetSize: 'Tilbakestill størrelse',
16 resizer: 'Klikk og dra for å endre størrelse',
17 title: 'Bildeegenskaper',
18 uploadTab: 'Last opp',
19 urlMissing: 'Bildets adresse mangler.',
20 altMissing: 'Alternativ tekst mangler.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/nl.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/nl.js new file mode 100644 index 0000000..925e0aa --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/nl.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'nl', {
6 alt: 'Alternatieve tekst',
7 btnUpload: 'Naar server verzenden',
8 captioned: 'Afbeelding met onderschrift',
9 captionPlaceholder: 'Onderschrift',
10 infoTab: 'Afbeeldingsinformatie',
11 lockRatio: 'Verhouding vergrendelen',
12 menu: 'Eigenschappen afbeelding',
13 pathName: 'afbeelding',
14 pathNameCaption: 'onderschrift',
15 resetSize: 'Afmetingen herstellen',
16 resizer: 'Klik en sleep om te herschalen',
17 title: 'Afbeeldingseigenschappen',
18 uploadTab: 'Uploaden',
19 urlMissing: 'De URL naar de afbeelding ontbreekt.',
20 altMissing: 'Alternatieve tekst ontbreekt.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/no.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/no.js new file mode 100644 index 0000000..1d43e04 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/no.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'no', {
6 alt: 'Alternativ tekst',
7 btnUpload: 'Send det til serveren',
8 captioned: 'Bilde med bildetekst',
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Bildeinformasjon',
11 lockRatio: 'Lås forhold',
12 menu: 'Bildeegenskaper',
13 pathName: 'bilde',
14 pathNameCaption: 'bildetekst',
15 resetSize: 'Tilbakestill størrelse',
16 resizer: 'Klikk og dra for å endre størrelse',
17 title: 'Bildeegenskaper',
18 uploadTab: 'Last opp',
19 urlMissing: 'Bildets adresse mangler.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/oc.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/oc.js new file mode 100644 index 0000000..1b75050 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/oc.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'oc', {
6 alt: 'Tèxte alternatiu',
7 btnUpload: 'Mandar sul servidor',
8 captioned: 'Imatge amb legenda',
9 captionPlaceholder: 'Legenda',
10 infoTab: 'Informacions sus l\'imatge',
11 lockRatio: 'Conservar las proporcions',
12 menu: 'Proprietats de l\'imatge',
13 pathName: 'imatge',
14 pathNameCaption: 'legenda',
15 resetSize: 'Reïnicializar la talha',
16 resizer: 'Clicar e lisar per redimensionar',
17 title: 'Proprietats de l\'imatge',
18 uploadTab: 'Mandar',
19 urlMissing: 'L\'URL font de l\'imatge es mancanta.',
20 altMissing: 'Lo tèxte alternatiu es mancant.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/pl.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/pl.js new file mode 100644 index 0000000..17e4d95 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/pl.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'pl', {
6 alt: 'Tekst zastępczy',
7 btnUpload: 'Wyślij',
8 captioned: 'Obrazek z podpisem',
9 captionPlaceholder: 'Podpis',
10 infoTab: 'Informacje o obrazku',
11 lockRatio: 'Zablokuj proporcje',
12 menu: 'Właściwości obrazka',
13 pathName: 'obrazek',
14 pathNameCaption: 'podpis',
15 resetSize: 'Przywróć rozmiar',
16 resizer: 'Kliknij i przeciągnij, by zmienić rozmiar.',
17 title: 'Właściwości obrazka',
18 uploadTab: 'Wyślij',
19 urlMissing: 'Podaj adres URL obrazka.',
20 altMissing: 'Podaj tekst zastępczy obrazka.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/pt-br.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/pt-br.js new file mode 100644 index 0000000..63d0023 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/pt-br.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'pt-br', {
6 alt: 'Texto Alternativo',
7 btnUpload: 'Enviar para o Servidor',
8 captioned: 'Legenda da Imagem',
9 captionPlaceholder: 'Legenda',
10 infoTab: 'Informações da Imagem',
11 lockRatio: 'Travar Proporções',
12 menu: 'Formatar Imagem',
13 pathName: 'Imagem',
14 pathNameCaption: 'Legenda',
15 resetSize: 'Redefinir para o Tamanho Original',
16 resizer: 'Click e arraste para redimensionar',
17 title: 'Formatar Imagem',
18 uploadTab: 'Enviar ao Servidor',
19 urlMissing: 'URL da imagem está faltando.',
20 altMissing: 'Texto alternativo não informado.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/pt.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/pt.js new file mode 100644 index 0000000..2621a07 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/pt.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'pt', {
6 alt: 'Texto alternativo',
7 btnUpload: 'Enviar para o servidor',
8 captioned: 'Imagem legendada',
9 captionPlaceholder: 'Legenda',
10 infoTab: 'Informação da imagem',
11 lockRatio: 'Proporcional',
12 menu: 'Propriedades da imagem',
13 pathName: 'imagem',
14 pathNameCaption: 'legenda',
15 resetSize: 'Tamanho original',
16 resizer: 'Clique e arraste para redimensionar',
17 title: 'Propriedades da imagem',
18 uploadTab: 'Carregar',
19 urlMissing: 'O URL de origem da imagem está em falta.',
20 altMissing: 'Texto alternativo em falta.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ro.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ro.js new file mode 100644 index 0000000..ed14bee --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ro.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ro', {
6 alt: 'Text alternativ',
7 btnUpload: 'Trimite la server',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Informaţii despre imagine',
11 lockRatio: 'Păstrează proporţiile',
12 menu: 'Proprietăţile imaginii',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Resetează mărimea',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Proprietăţile imaginii',
18 uploadTab: 'Încarcă',
19 urlMissing: 'Sursa URL a imaginii lipsește.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ru.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ru.js new file mode 100644 index 0000000..eeafd51 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ru.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ru', {
6 alt: 'Альтернативный текст',
7 btnUpload: 'Загрузить на сервер',
8 captioned: 'Отображать название',
9 captionPlaceholder: 'Название',
10 infoTab: 'Данные об изображении',
11 lockRatio: 'Сохранять пропорции',
12 menu: 'Свойства изображения',
13 pathName: 'изображение',
14 pathNameCaption: 'название',
15 resetSize: 'Вернуть обычные размеры',
16 resizer: 'Нажмите и растяните',
17 title: 'Свойства изображения',
18 uploadTab: 'Загрузка файла',
19 urlMissing: 'Не указана ссылка на изображение.',
20 altMissing: 'Не задан альтернативный текст'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/si.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/si.js new file mode 100644 index 0000000..d86b2d6 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/si.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'si', {
6 alt: 'විකල්ප ',
7 btnUpload: 'සේවාදායකය වෙත යොමුකිරිම',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'රුපයේ තොරතුරු',
11 lockRatio: 'නවතන අනුපාතය ',
12 menu: 'රුපයේ ගුණ',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'නැවතත් විශාලත්වය වෙනස් කිරීම',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'රුපයේ ',
18 uploadTab: 'උඩුගතකිරීම',
19 urlMissing: 'රුප මුලාශ්‍ර URL නැත.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/sk.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/sk.js new file mode 100644 index 0000000..dac2032 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/sk.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'sk', {
6 alt: 'Alternatívny text',
7 btnUpload: 'Odoslať to na server',
8 captioned: 'Opísaný obrázok',
9 captionPlaceholder: 'Popis',
10 infoTab: 'Informácie o obrázku',
11 lockRatio: 'Pomer zámky',
12 menu: 'Vlastnosti obrázka',
13 pathName: 'obrázok',
14 pathNameCaption: 'popis',
15 resetSize: 'Pôvodná veľkosť',
16 resizer: 'Kliknite a potiahnite pre zmenu veľkosti',
17 title: 'Vlastnosti obrázka',
18 uploadTab: 'Nahrať',
19 urlMissing: 'Chýba URL zdroja obrázka.',
20 altMissing: 'Chýba alternatívny text.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/sl.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/sl.js new file mode 100644 index 0000000..548ad59 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/sl.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'sl', {
6 alt: 'Nadomestno besedilo',
7 btnUpload: 'Pošlji na strežnik',
8 captioned: 'Slika z napisom',
9 captionPlaceholder: 'Napis',
10 infoTab: 'Podatki o sliki',
11 lockRatio: 'Zakleni razmerje',
12 menu: 'Lastnosti slike',
13 pathName: 'slika',
14 pathNameCaption: 'napis',
15 resetSize: 'Ponastavi velikost',
16 resizer: 'Kliknite in povlecite, da spremenite velikost',
17 title: 'Lastnosti slike',
18 uploadTab: 'Naloži',
19 urlMissing: 'Manjka vir (URL) slike.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/sq.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/sq.js new file mode 100644 index 0000000..e10f33c --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/sq.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'sq', {
6 alt: 'Tekst Alternativ',
7 btnUpload: 'Dërgo në server',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Informacione mbi Fotografinë',
11 lockRatio: 'Mbyll Racionin',
12 menu: 'Karakteristikat e Fotografisë',
13 pathName: 'foto',
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Rikthe Madhësinë',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Karakteristikat e Fotografisë',
18 uploadTab: 'Ngarko',
19 urlMissing: 'Mungon URL e burimit të fotografisë.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/sr-latn.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/sr-latn.js new file mode 100644 index 0000000..cd0975b --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/sr-latn.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'sr-latn', {
6 alt: 'Alternativni tekst',
7 btnUpload: 'Pošalji na server',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Info slike',
11 lockRatio: 'Zaključaj odnos',
12 menu: 'Osobine slika',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Resetuj veličinu',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Osobine slika',
18 uploadTab: 'Pošalji',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/sr.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/sr.js new file mode 100644 index 0000000..9544b91 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/sr.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'sr', {
6 alt: 'Алтернативни текст',
7 btnUpload: 'Пошаљи на сервер',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'Инфо слике',
11 lockRatio: 'Закључај однос',
12 menu: 'Особине слика',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'Ресетуј величину',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'Особине слика',
18 uploadTab: 'Пошаљи',
19 urlMissing: 'Недостаје УРЛ слике.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/sv.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/sv.js new file mode 100644 index 0000000..36fd8cb --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/sv.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'sv', {
6 alt: 'Alternativ text',
7 btnUpload: 'Skicka till server',
8 captioned: 'Rubricerad bild',
9 captionPlaceholder: 'Bildtext',
10 infoTab: 'Bildinformation',
11 lockRatio: 'Lås höjd/bredd förhållanden',
12 menu: 'Bildegenskaper',
13 pathName: 'bild',
14 pathNameCaption: 'rubrik',
15 resetSize: 'Återställ storlek',
16 resizer: 'Klicka och drag för att ändra storlek',
17 title: 'Bildegenskaper',
18 uploadTab: 'Ladda upp',
19 urlMissing: 'Bildkällans URL saknas.',
20 altMissing: 'Alternativ text saknas'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/th.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/th.js new file mode 100644 index 0000000..48d99a9 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/th.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'th', {
6 alt: 'คำประกอบรูปภาพ',
7 btnUpload: 'อัพโหลดไฟล์ไปเก็บไว้ที่เครื่องแม่ข่าย (เซิร์ฟเวอร์)',
8 captioned: 'Captioned image', // MISSING
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'ข้อมูลของรูปภาพ',
11 lockRatio: 'กำหนดอัตราส่วน กว้าง-สูง แบบคงที่',
12 menu: 'คุณสมบัติของ รูปภาพ',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'กำหนดรูปเท่าขนาดจริง',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'คุณสมบัติของ รูปภาพ',
18 uploadTab: 'อัพโหลดไฟล์',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/tr.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/tr.js new file mode 100644 index 0000000..c358582 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/tr.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'tr', {
6 alt: 'Alternatif Yazı',
7 btnUpload: 'Sunucuya Yolla',
8 captioned: 'Başlıklı resim',
9 captionPlaceholder: 'Başlık',
10 infoTab: 'Resim Bilgisi',
11 lockRatio: 'Oranı Kilitle',
12 menu: 'Resim Özellikleri',
13 pathName: 'Resim',
14 pathNameCaption: 'başlık',
15 resetSize: 'Boyutu Başa Döndür',
16 resizer: 'Boyutlandırmak için, tıklayın ve sürükleyin',
17 title: 'Resim Özellikleri',
18 uploadTab: 'Karşıya Yükle',
19 urlMissing: 'Resmin URL kaynağı bulunamadı.',
20 altMissing: 'Alternatif yazı eksik.'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/tt.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/tt.js new file mode 100644 index 0000000..2c0dca5 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/tt.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'tt', {
6 alt: 'Альтернатив текст',
7 btnUpload: 'Серверга җибәрү',
8 captioned: 'Исеме куелган рәсем',
9 captionPlaceholder: 'Исем',
10 infoTab: 'Рәсем тасвирламасы',
11 lockRatio: 'Lock Ratio', // MISSING
12 menu: 'Рәсем үзлекләре',
13 pathName: 'рәсем',
14 pathNameCaption: 'исем',
15 resetSize: 'Баштагы зурлык',
16 resizer: 'Күчереп куер өчен басып шудырыгыз',
17 title: 'Рәсем үзлекләре',
18 uploadTab: 'Йөкләү',
19 urlMissing: 'Image source URL is missing.', // MISSING
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/ug.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/ug.js new file mode 100644 index 0000000..af146f2 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/ug.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'ug', {
6 alt: 'تېكىست ئالماشتۇر',
7 btnUpload: 'مۇلازىمېتىرغا يۈكلە',
8 captioned: 'ماۋزۇلۇق سۈرەت',
9 captionPlaceholder: 'Caption', // MISSING
10 infoTab: 'سۈرەت',
11 lockRatio: 'نىسبەتنى قۇلۇپلا',
12 menu: 'سۈرەت خاسلىقى',
13 pathName: 'image', // MISSING
14 pathNameCaption: 'caption', // MISSING
15 resetSize: 'ئەسلى چوڭلۇق',
16 resizer: 'Click and drag to resize', // MISSING
17 title: 'سۈرەت خاسلىقى',
18 uploadTab: 'يۈكلە',
19 urlMissing: 'سۈرەتنىڭ ئەسلى ھۆججەت ئادرېسى كەم',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/uk.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/uk.js new file mode 100644 index 0000000..1cf3cac --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/uk.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'uk', {
6 alt: 'Альтернативний текст',
7 btnUpload: 'Надіслати на сервер',
8 captioned: 'Підписане зображення',
9 captionPlaceholder: 'Заголовок',
10 infoTab: 'Інформація про зображення',
11 lockRatio: 'Зберегти пропорції',
12 menu: 'Властивості зображення',
13 pathName: 'Зображення',
14 pathNameCaption: 'заголовок',
15 resetSize: 'Очистити поля розмірів',
16 resizer: 'Клікніть та потягніть для зміни розмірів',
17 title: 'Властивості зображення',
18 uploadTab: 'Надіслати',
19 urlMissing: 'Вкажіть URL зображення.',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/vi.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/vi.js new file mode 100644 index 0000000..7a40fbf --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/vi.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'vi', {
6 alt: 'Chú thích ảnh',
7 btnUpload: 'Tải lên máy chủ',
8 captioned: 'Ảnh có chú thích',
9 captionPlaceholder: 'Nhãn',
10 infoTab: 'Thông tin của ảnh',
11 lockRatio: 'Giữ nguyên tỷ lệ',
12 menu: 'Thuộc tính của ảnh',
13 pathName: 'ảnh',
14 pathNameCaption: 'chú thích',
15 resetSize: 'Kích thước gốc',
16 resizer: 'Kéo rê để thay đổi kích cỡ',
17 title: 'Thuộc tính của ảnh',
18 uploadTab: 'Tải lên',
19 urlMissing: 'Thiếu đường dẫn hình ảnh',
20 altMissing: 'Alternative text is missing.' // MISSING
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/zh-cn.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/zh-cn.js new file mode 100644 index 0000000..48c436f --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/zh-cn.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'zh-cn', {
6 alt: '替换文本',
7 btnUpload: '上传到服务器',
8 captioned: '带标题图像',
9 captionPlaceholder: '标题',
10 infoTab: '图像信息',
11 lockRatio: '锁定比例',
12 menu: '图像属性',
13 pathName: '图像',
14 pathNameCaption: '标题',
15 resetSize: '原始尺寸',
16 resizer: '点击并拖拽以改变尺寸',
17 title: '图像属性',
18 uploadTab: '上传',
19 urlMissing: '缺少图像源文件地址',
20 altMissing: '缺少替换文本'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/lang/zh.js b/app/assets/javascripts/ckeditor/plugins/image2/lang/zh.js new file mode 100644 index 0000000..1578b41 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/lang/zh.js
@@ -0,0 +1,21 @@
1/*
2Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.md or http://ckeditor.com/license
4*/
5CKEDITOR.plugins.setLang( 'image2', 'zh', {
6 alt: '替代文字',
7 btnUpload: '傳送至伺服器',
8 captioned: '已加標題之圖片',
9 captionPlaceholder: '標題',
10 infoTab: '影像資訊',
11 lockRatio: '固定比例',
12 menu: '影像屬性',
13 pathName: '圖片',
14 pathNameCaption: '標題',
15 resetSize: '重設大小',
16 resizer: '拖曳以改變大小',
17 title: '影像屬性',
18 uploadTab: '上傳',
19 urlMissing: '遺失圖片來源之 URL ',
20 altMissing: '替代文字遺失。'
21} );
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/plugin.js b/app/assets/javascripts/ckeditor/plugins/image2/plugin.js new file mode 100644 index 0000000..3a55255 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/plugin.js
@@ -0,0 +1,1712 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6'use strict';
7
8( function() {
9
10 var template = '<img alt="" src="" />',
11 templateBlock = new CKEDITOR.template(
12 '<figure class="{captionedClass}">' +
13 template +
14 '<figcaption>{captionPlaceholder}</figcaption>' +
15 '</figure>' ),
16 alignmentsObj = { left: 0, center: 1, right: 2 },
17 regexPercent = /^\s*(\d+\%)\s*$/i;
18
19 CKEDITOR.plugins.add( 'image2', {
20 // jscs:disable maximumLineLength
21 lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,es-mx,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,oc,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
22 // jscs:enable maximumLineLength
23 requires: 'widget,dialog',
24 icons: 'image',
25 hidpi: true,
26
27 onLoad: function() {
28 CKEDITOR.addCss(
29 '.cke_image_nocaption{' +
30 // This is to remove unwanted space so resize
31 // wrapper is displayed property.
32 'line-height:0' +
33 '}' +
34 '.cke_editable.cke_image_sw, .cke_editable.cke_image_sw *{cursor:sw-resize !important}' +
35 '.cke_editable.cke_image_se, .cke_editable.cke_image_se *{cursor:se-resize !important}' +
36 '.cke_image_resizer{' +
37 'display:none;' +
38 'position:absolute;' +
39 'width:10px;' +
40 'height:10px;' +
41 'bottom:-5px;' +
42 'right:-5px;' +
43 'background:#000;' +
44 'outline:1px solid #fff;' +
45 // Prevent drag handler from being misplaced (http://dev.ckeditor.com/ticket/11207).
46 'line-height:0;' +
47 'cursor:se-resize;' +
48 '}' +
49 '.cke_image_resizer_wrapper{' +
50 'position:relative;' +
51 'display:inline-block;' +
52 'line-height:0;' +
53 '}' +
54 // Bottom-left corner style of the resizer.
55 '.cke_image_resizer.cke_image_resizer_left{' +
56 'right:auto;' +
57 'left:-5px;' +
58 'cursor:sw-resize;' +
59 '}' +
60 '.cke_widget_wrapper:hover .cke_image_resizer,' +
61 '.cke_image_resizer.cke_image_resizing{' +
62 'display:block' +
63 '}' +
64 // Expand widget wrapper when linked inline image.
65 '.cke_widget_wrapper>a{' +
66 'display:inline-block' +
67 '}' );
68 },
69
70 init: function( editor ) {
71 // Adapts configuration from original image plugin. Should be removed
72 // when we'll rename image2 to image.
73 var config = editor.config,
74 lang = editor.lang.image2,
75 image = widgetDef( editor );
76
77 // Since filebrowser plugin discovers config properties by dialog (plugin?)
78 // names (sic!), this hack will be necessary as long as Image2 is not named
79 // Image. And since Image2 will never be Image, for sure some filebrowser logic
80 // got to be refined.
81 config.filebrowserImage2BrowseUrl = config.filebrowserImageBrowseUrl;
82 config.filebrowserImage2UploadUrl = config.filebrowserImageUploadUrl;
83
84 // Add custom elementspath names to widget definition.
85 image.pathName = lang.pathName;
86 image.editables.caption.pathName = lang.pathNameCaption;
87
88 // Register the widget.
89 editor.widgets.add( 'image', image );
90
91 // Add toolbar button for this plugin.
92 editor.ui.addButton && editor.ui.addButton( 'Image', {
93 label: editor.lang.common.image,
94 command: 'image',
95 toolbar: 'insert,10'
96 } );
97
98 // Register context menu option for editing widget.
99 if ( editor.contextMenu ) {
100 editor.addMenuGroup( 'image', 10 );
101
102 editor.addMenuItem( 'image', {
103 label: lang.menu,
104 command: 'image',
105 group: 'image'
106 } );
107 }
108
109 CKEDITOR.dialog.add( 'image2', this.path + 'dialogs/image2.js' );
110 },
111
112 afterInit: function( editor ) {
113 // Integrate with align commands (justify plugin).
114 var align = { left: 1, right: 1, center: 1, block: 1 },
115 integrate = alignCommandIntegrator( editor );
116
117 for ( var value in align )
118 integrate( value );
119
120 // Integrate with link commands (link plugin).
121 linkCommandIntegrator( editor );
122 }
123 } );
124
125 // Wiget states (forms) depending on alignment and configuration.
126 //
127 // Non-captioned widget (inline styles)
128 // ┌──────┬───────────────────────────────┬─────────────────────────────┐
129 // │Align │Internal form │Data │
130 // ├──────┼───────────────────────────────┼─────────────────────────────┤
131 // │none │<wrapper> │<img /> │
132 // │ │ <img /> │ │
133 // │ │</wrapper> │ │
134 // ├──────┼───────────────────────────────┼─────────────────────────────┤
135 // │left │<wrapper style=”float:left”> │<img style=”float:left” /> │
136 // │ │ <img /> │ │
137 // │ │</wrapper> │ │
138 // ├──────┼───────────────────────────────┼─────────────────────────────┤
139 // │center│<wrapper> │<p style=”text-align:center”>│
140 // │ │ <p style=”text-align:center”> │ <img /> │
141 // │ │ <img /> │</p> │
142 // │ │ </p> │ │
143 // │ │</wrapper> │ │
144 // ├──────┼───────────────────────────────┼─────────────────────────────┤
145 // │right │<wrapper style=”float:right”> │<img style=”float:right” /> │
146 // │ │ <img /> │ │
147 // │ │</wrapper> │ │
148 // └──────┴───────────────────────────────┴─────────────────────────────┘
149 //
150 // Non-captioned widget (config.image2_alignClasses defined)
151 // ┌──────┬───────────────────────────────┬─────────────────────────────┐
152 // │Align │Internal form │Data │
153 // ├──────┼───────────────────────────────┼─────────────────────────────┤
154 // │none │<wrapper> │<img /> │
155 // │ │ <img /> │ │
156 // │ │</wrapper> │ │
157 // ├──────┼───────────────────────────────┼─────────────────────────────┤
158 // │left │<wrapper class=”left”> │<img class=”left” /> │
159 // │ │ <img /> │ │
160 // │ │</wrapper> │ │
161 // ├──────┼───────────────────────────────┼─────────────────────────────┤
162 // │center│<wrapper> │<p class=”center”> │
163 // │ │ <p class=”center”> │ <img /> │
164 // │ │ <img /> │</p> │
165 // │ │ </p> │ │
166 // │ │</wrapper> │ │
167 // ├──────┼───────────────────────────────┼─────────────────────────────┤
168 // │right │<wrapper class=”right”> │<img class=”right” /> │
169 // │ │ <img /> │ │
170 // │ │</wrapper> │ │
171 // └──────┴───────────────────────────────┴─────────────────────────────┘
172 //
173 // Captioned widget (inline styles)
174 // ┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
175 // │Align │Internal form │Data │
176 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
177 // │none │<wrapper> │<figure /> │
178 // │ │ <figure /> │ │
179 // │ │</wrapper> │ │
180 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
181 // │left │<wrapper style=”float:left”> │<figure style=”float:left” /> │
182 // │ │ <figure /> │ │
183 // │ │</wrapper> │ │
184 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
185 // │center│<wrapper style=”text-align:center”> │<div style=”text-align:center”> │
186 // │ │ <figure style=”display:inline-block” />│ <figure style=”display:inline-block” />│
187 // │ │</wrapper> │</p> │
188 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
189 // │right │<wrapper style=”float:right”> │<figure style=”float:right” /> │
190 // │ │ <figure /> │ │
191 // │ │</wrapper> │ │
192 // └──────┴────────────────────────────────────────┴────────────────────────────────────────┘
193 //
194 // Captioned widget (config.image2_alignClasses defined)
195 // ┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
196 // │Align │Internal form │Data │
197 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
198 // │none │<wrapper> │<figure /> │
199 // │ │ <figure /> │ │
200 // │ │</wrapper> │ │
201 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
202 // │left │<wrapper class=”left”> │<figure class=”left” /> │
203 // │ │ <figure /> │ │
204 // │ │</wrapper> │ │
205 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
206 // │center│<wrapper class=”center”> │<div class=”center”> │
207 // │ │ <figure /> │ <figure /> │
208 // │ │</wrapper> │</p> │
209 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
210 // │right │<wrapper class=”right”> │<figure class=”right” /> │
211 // │ │ <figure /> │ │
212 // │ │</wrapper> │ │
213 // └──────┴────────────────────────────────────────┴────────────────────────────────────────┘
214 //
215 // @param {CKEDITOR.editor}
216 // @returns {Object}
217 function widgetDef( editor ) {
218 var alignClasses = editor.config.image2_alignClasses,
219 captionedClass = editor.config.image2_captionedClass;
220
221 function deflate() {
222 if ( this.deflated )
223 return;
224
225 // Remember whether widget was focused before destroyed.
226 if ( editor.widgets.focused == this.widget )
227 this.focused = true;
228
229 editor.widgets.destroy( this.widget );
230
231 // Mark widget was destroyed.
232 this.deflated = true;
233 }
234
235 function inflate() {
236 var editable = editor.editable(),
237 doc = editor.document;
238
239 // Create a new widget. This widget will be either captioned
240 // non-captioned, block or inline according to what is the
241 // new state of the widget.
242 if ( this.deflated ) {
243 this.widget = editor.widgets.initOn( this.element, 'image', this.widget.data );
244
245 // Once widget was re-created, it may become an inline element without
246 // block wrapper (i.e. when unaligned, end not captioned). Let's do some
247 // sort of autoparagraphing here (http://dev.ckeditor.com/ticket/10853).
248 if ( this.widget.inline && !( new CKEDITOR.dom.elementPath( this.widget.wrapper, editable ).block ) ) {
249 var block = doc.createElement( editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
250 block.replace( this.widget.wrapper );
251 this.widget.wrapper.move( block );
252 }
253
254 // The focus must be transferred from the old one (destroyed)
255 // to the new one (just created).
256 if ( this.focused ) {
257 this.widget.focus();
258 delete this.focused;
259 }
260
261 delete this.deflated;
262 }
263
264 // If now widget was destroyed just update wrapper's alignment.
265 // According to the new state.
266 else {
267 setWrapperAlign( this.widget, alignClasses );
268 }
269 }
270
271 return {
272 allowedContent: getWidgetAllowedContent( editor ),
273
274 requiredContent: 'img[src,alt]',
275
276 features: getWidgetFeatures( editor ),
277
278 styleableElements: 'img figure',
279
280 // This widget converts style-driven dimensions to attributes.
281 contentTransformations: [
282 [ 'img[width]: sizeToAttribute' ]
283 ],
284
285 // This widget has an editable caption.
286 editables: {
287 caption: {
288 selector: 'figcaption',
289 allowedContent: 'br em strong sub sup u s; a[!href,target]'
290 }
291 },
292
293 parts: {
294 image: 'img',
295 caption: 'figcaption'
296 // parts#link defined in widget#init
297 },
298
299 // The name of this widget's dialog.
300 dialog: 'image2',
301
302 // Template of the widget: plain image.
303 template: template,
304
305 data: function() {
306 var features = this.features;
307
308 // Image can't be captioned when figcaption is disallowed (http://dev.ckeditor.com/ticket/11004).
309 if ( this.data.hasCaption && !editor.filter.checkFeature( features.caption ) )
310 this.data.hasCaption = false;
311
312 // Image can't be aligned when floating is disallowed (http://dev.ckeditor.com/ticket/11004).
313 if ( this.data.align != 'none' && !editor.filter.checkFeature( features.align ) )
314 this.data.align = 'none';
315
316 // Convert the internal form of the widget from the old state to the new one.
317 this.shiftState( {
318 widget: this,
319 element: this.element,
320 oldData: this.oldData,
321 newData: this.data,
322 deflate: deflate,
323 inflate: inflate
324 } );
325
326 // Update widget.parts.link since it will not auto-update unless widget
327 // is destroyed and re-inited.
328 if ( !this.data.link ) {
329 if ( this.parts.link )
330 delete this.parts.link;
331 } else {
332 if ( !this.parts.link )
333 this.parts.link = this.parts.image.getParent();
334 }
335
336 this.parts.image.setAttributes( {
337 src: this.data.src,
338
339 // This internal is required by the editor.
340 'data-cke-saved-src': this.data.src,
341
342 alt: this.data.alt
343 } );
344
345 // If shifting non-captioned -> captioned, remove classes
346 // related to styles from <img/>.
347 if ( this.oldData && !this.oldData.hasCaption && this.data.hasCaption ) {
348 for ( var c in this.data.classes )
349 this.parts.image.removeClass( c );
350 }
351
352 // Set dimensions of the image according to gathered data.
353 // Do it only when the attributes are allowed (http://dev.ckeditor.com/ticket/11004).
354 if ( editor.filter.checkFeature( features.dimension ) )
355 setDimensions( this );
356
357 // Cache current data.
358 this.oldData = CKEDITOR.tools.extend( {}, this.data );
359 },
360
361 init: function() {
362 var helpers = CKEDITOR.plugins.image2,
363 image = this.parts.image,
364 data = {
365 hasCaption: !!this.parts.caption,
366 src: image.getAttribute( 'src' ),
367 alt: image.getAttribute( 'alt' ) || '',
368 width: image.getAttribute( 'width' ) || '',
369 height: image.getAttribute( 'height' ) || '',
370
371 // Lock ratio is on by default (http://dev.ckeditor.com/ticket/10833).
372 lock: this.ready ? helpers.checkHasNaturalRatio( image ) : true
373 };
374
375 // If we used 'a' in widget#parts definition, it could happen that
376 // selected element is a child of widget.parts#caption. Since there's no clever
377 // way to solve it with CSS selectors, it's done like that. (http://dev.ckeditor.com/ticket/11783).
378 var link = image.getAscendant( 'a' );
379
380 if ( link && this.wrapper.contains( link ) )
381 this.parts.link = link;
382
383 // Depending on configuration, read style/class from element and
384 // then remove it. Removed style/class will be set on wrapper in #data listener.
385 // Note: Center alignment is detected during upcast, so only left/right cases
386 // are checked below.
387 if ( !data.align ) {
388 var alignElement = data.hasCaption ? this.element : image;
389
390 // Read the initial left/right alignment from the class set on element.
391 if ( alignClasses ) {
392 if ( alignElement.hasClass( alignClasses[ 0 ] ) ) {
393 data.align = 'left';
394 } else if ( alignElement.hasClass( alignClasses[ 2 ] ) ) {
395 data.align = 'right';
396 }
397
398 if ( data.align ) {
399 alignElement.removeClass( alignClasses[ alignmentsObj[ data.align ] ] );
400 } else {
401 data.align = 'none';
402 }
403 }
404 // Read initial float style from figure/image and then remove it.
405 else {
406 data.align = alignElement.getStyle( 'float' ) || 'none';
407 alignElement.removeStyle( 'float' );
408 }
409 }
410
411 // Update data.link object with attributes if the link has been discovered.
412 if ( editor.plugins.link && this.parts.link ) {
413 data.link = helpers.getLinkAttributesParser()( editor, this.parts.link );
414
415 // Get rid of cke_widget_* classes in data. Otherwise
416 // they might appear in link dialog.
417 var advanced = data.link.advanced;
418 if ( advanced && advanced.advCSSClasses ) {
419 advanced.advCSSClasses = CKEDITOR.tools.trim( advanced.advCSSClasses.replace( /cke_\S+/, '' ) );
420 }
421 }
422
423 // Get rid of extra vertical space when there's no caption.
424 // It will improve the look of the resizer.
425 this.wrapper[ ( data.hasCaption ? 'remove' : 'add' ) + 'Class' ]( 'cke_image_nocaption' );
426
427 this.setData( data );
428
429 // Setup dynamic image resizing with mouse.
430 // Don't initialize resizer when dimensions are disallowed (http://dev.ckeditor.com/ticket/11004).
431 if ( editor.filter.checkFeature( this.features.dimension ) && editor.config.image2_disableResizer !== true )
432 setupResizer( this );
433
434 this.shiftState = helpers.stateShifter( this.editor );
435
436 // Add widget editing option to its context menu.
437 this.on( 'contextMenu', function( evt ) {
438 evt.data.image = CKEDITOR.TRISTATE_OFF;
439
440 // Integrate context menu items for link.
441 // Note that widget may be wrapped in a link, which
442 // does not belong to that widget (http://dev.ckeditor.com/ticket/11814).
443 if ( this.parts.link || this.wrapper.getAscendant( 'a' ) )
444 evt.data.link = evt.data.unlink = CKEDITOR.TRISTATE_OFF;
445 } );
446
447 // Pass the reference to this widget to the dialog.
448 this.on( 'dialog', function( evt ) {
449 evt.data.widget = this;
450 }, this );
451 },
452
453 // Overrides default method to handle internal mutability of Image2.
454 // @see CKEDITOR.plugins.widget#addClass
455 addClass: function( className ) {
456 getStyleableElement( this ).addClass( className );
457 },
458
459 // Overrides default method to handle internal mutability of Image2.
460 // @see CKEDITOR.plugins.widget#hasClass
461 hasClass: function( className ) {
462 return getStyleableElement( this ).hasClass( className );
463 },
464
465 // Overrides default method to handle internal mutability of Image2.
466 // @see CKEDITOR.plugins.widget#removeClass
467 removeClass: function( className ) {
468 getStyleableElement( this ).removeClass( className );
469 },
470
471 // Overrides default method to handle internal mutability of Image2.
472 // @see CKEDITOR.plugins.widget#getClasses
473 getClasses: ( function() {
474 var classRegex = new RegExp( '^(' + [].concat( captionedClass, alignClasses ).join( '|' ) + ')$' );
475
476 return function() {
477 var classes = this.repository.parseElementClasses( getStyleableElement( this ).getAttribute( 'class' ) );
478
479 // Neither config.image2_captionedClass nor config.image2_alignClasses
480 // do not belong to style classes.
481 for ( var c in classes ) {
482 if ( classRegex.test( c ) )
483 delete classes[ c ];
484 }
485
486 return classes;
487 };
488 } )(),
489
490 upcast: upcastWidgetElement( editor ),
491 downcast: downcastWidgetElement( editor ),
492
493 getLabel: function() {
494 var label = ( this.data.alt || '' ) + ' ' + this.pathName;
495
496 return this.editor.lang.widget.label.replace( /%1/, label );
497 }
498 };
499 }
500
501 /**
502 * A set of Enhanced Image (image2) plugin helpers.
503 *
504 * @class
505 * @singleton
506 */
507 CKEDITOR.plugins.image2 = {
508 stateShifter: function( editor ) {
509 // Tag name used for centering non-captioned widgets.
510 var doc = editor.document,
511 alignClasses = editor.config.image2_alignClasses,
512 captionedClass = editor.config.image2_captionedClass,
513 editable = editor.editable(),
514
515 // The order that stateActions get executed. It matters!
516 shiftables = [ 'hasCaption', 'align', 'link' ];
517
518 // Atomic procedures, one per state variable.
519 var stateActions = {
520 align: function( shift, oldValue, newValue ) {
521 var el = shift.element;
522
523 // Alignment changed.
524 if ( shift.changed.align ) {
525 // No caption in the new state.
526 if ( !shift.newData.hasCaption ) {
527 // Changed to "center" (non-captioned).
528 if ( newValue == 'center' ) {
529 shift.deflate();
530 shift.element = wrapInCentering( editor, el );
531 }
532
533 // Changed to "non-center" from "center" while caption removed.
534 if ( !shift.changed.hasCaption && oldValue == 'center' && newValue != 'center' ) {
535 shift.deflate();
536 shift.element = unwrapFromCentering( el );
537 }
538 }
539 }
540
541 // Alignment remains and "center" removed caption.
542 else if ( newValue == 'center' && shift.changed.hasCaption && !shift.newData.hasCaption ) {
543 shift.deflate();
544 shift.element = wrapInCentering( editor, el );
545 }
546
547 // Finally set display for figure.
548 if ( !alignClasses && el.is( 'figure' ) ) {
549 if ( newValue == 'center' )
550 el.setStyle( 'display', 'inline-block' );
551 else
552 el.removeStyle( 'display' );
553 }
554 },
555
556 hasCaption: function( shift, oldValue, newValue ) {
557 // This action is for real state change only.
558 if ( !shift.changed.hasCaption )
559 return;
560
561 // Get <img/> or <a><img/></a> from widget. Note that widget element might itself
562 // be what we're looking for. Also element can be <p style="text-align:center"><a>...</a></p>.
563 var imageOrLink;
564 if ( shift.element.is( { img: 1, a: 1 } ) )
565 imageOrLink = shift.element;
566 else
567 imageOrLink = shift.element.findOne( 'a,img' );
568
569 // Switching hasCaption always destroys the widget.
570 shift.deflate();
571
572 // There was no caption, but the caption is to be added.
573 if ( newValue ) {
574 // Create new <figure> from widget template.
575 var figure = CKEDITOR.dom.element.createFromHtml( templateBlock.output( {
576 captionedClass: captionedClass,
577 captionPlaceholder: editor.lang.image2.captionPlaceholder
578 } ), doc );
579
580 // Replace element with <figure>.
581 replaceSafely( figure, shift.element );
582
583 // Use old <img/> or <a><img/></a> instead of the one from the template,
584 // so we won't lose additional attributes.
585 imageOrLink.replace( figure.findOne( 'img' ) );
586
587 // Update widget's element.
588 shift.element = figure;
589 }
590
591 // The caption was present, but now it's to be removed.
592 else {
593 // Unwrap <img/> or <a><img/></a> from figure.
594 imageOrLink.replace( shift.element );
595
596 // Update widget's element.
597 shift.element = imageOrLink;
598 }
599 },
600
601 link: function( shift, oldValue, newValue ) {
602 if ( shift.changed.link ) {
603 var img = shift.element.is( 'img' ) ?
604 shift.element : shift.element.findOne( 'img' ),
605 link = shift.element.is( 'a' ) ?
606 shift.element : shift.element.findOne( 'a' ),
607 // Why deflate:
608 // If element is <img/>, it will be wrapped into <a>,
609 // which becomes a new widget.element.
610 // If element is <a><img/></a>, it will be unlinked
611 // so <img/> becomes a new widget.element.
612 needsDeflate = ( shift.element.is( 'a' ) && !newValue ) || ( shift.element.is( 'img' ) && newValue ),
613 newEl;
614
615 if ( needsDeflate )
616 shift.deflate();
617
618 // If unlinked the image, returned element is <img>.
619 if ( !newValue )
620 newEl = unwrapFromLink( link );
621 else {
622 // If linked the image, returned element is <a>.
623 if ( !oldValue )
624 newEl = wrapInLink( img, shift.newData.link );
625
626 // Set and remove all attributes associated with this state.
627 var attributes = CKEDITOR.plugins.image2.getLinkAttributesGetter()( editor, newValue );
628
629 if ( !CKEDITOR.tools.isEmpty( attributes.set ) )
630 ( newEl || link ).setAttributes( attributes.set );
631
632 if ( attributes.removed.length )
633 ( newEl || link ).removeAttributes( attributes.removed );
634 }
635
636 if ( needsDeflate )
637 shift.element = newEl;
638 }
639 }
640 };
641
642 function wrapInCentering( editor, element ) {
643 var attribsAndStyles = {};
644
645 if ( alignClasses )
646 attribsAndStyles.attributes = { 'class': alignClasses[ 1 ] };
647 else
648 attribsAndStyles.styles = { 'text-align': 'center' };
649
650 // There's no gentle way to center inline element with CSS, so create p/div
651 // that wraps widget contents and does the trick either with style or class.
652 var center = doc.createElement(
653 editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div', attribsAndStyles );
654
655 // Replace element with centering wrapper.
656 replaceSafely( center, element );
657 element.move( center );
658
659 return center;
660 }
661
662 function unwrapFromCentering( element ) {
663 var imageOrLink = element.findOne( 'a,img' );
664
665 imageOrLink.replace( element );
666
667 return imageOrLink;
668 }
669
670 // Wraps <img/> -> <a><img/></a>.
671 // Returns reference to <a>.
672 //
673 // @param {CKEDITOR.dom.element} img
674 // @param {Object} linkData
675 // @returns {CKEDITOR.dom.element}
676 function wrapInLink( img, linkData ) {
677 var link = doc.createElement( 'a', {
678 attributes: {
679 href: linkData.url
680 }
681 } );
682
683 link.replace( img );
684 img.move( link );
685
686 return link;
687 }
688
689 // De-wraps <a><img/></a> -> <img/>.
690 // Returns the reference to <img/>
691 //
692 // @param {CKEDITOR.dom.element} link
693 // @returns {CKEDITOR.dom.element}
694 function unwrapFromLink( link ) {
695 var img = link.findOne( 'img' );
696
697 img.replace( link );
698
699 return img;
700 }
701
702 function replaceSafely( replacing, replaced ) {
703 if ( replaced.getParent() ) {
704 var range = editor.createRange();
705
706 range.moveToPosition( replaced, CKEDITOR.POSITION_BEFORE_START );
707
708 // Remove old element. Do it before insertion to avoid a case when
709 // element is moved from 'replaced' element before it, what creates
710 // a tricky case which insertElementIntorRange does not handle.
711 replaced.remove();
712
713 editable.insertElementIntoRange( replacing, range );
714 }
715 else {
716 replacing.replace( replaced );
717 }
718 }
719
720 return function( shift ) {
721 var name, i;
722
723 shift.changed = {};
724
725 for ( i = 0; i < shiftables.length; i++ ) {
726 name = shiftables[ i ];
727
728 shift.changed[ name ] = shift.oldData ?
729 shift.oldData[ name ] !== shift.newData[ name ] : false;
730 }
731
732 // Iterate over possible state variables.
733 for ( i = 0; i < shiftables.length; i++ ) {
734 name = shiftables[ i ];
735
736 stateActions[ name ]( shift,
737 shift.oldData ? shift.oldData[ name ] : null,
738 shift.newData[ name ] );
739 }
740
741 shift.inflate();
742 };
743 },
744
745 /**
746 * Checks whether the current image ratio matches the natural one
747 * by comparing dimensions.
748 *
749 * @param {CKEDITOR.dom.element} image
750 * @returns {Boolean}
751 */
752 checkHasNaturalRatio: function( image ) {
753 var $ = image.$,
754 natural = this.getNatural( image );
755
756 // The reason for two alternative comparisons is that the rounding can come from
757 // both dimensions, e.g. there are two cases:
758 // 1. height is computed as a rounded relation of the real height and the value of width,
759 // 2. width is computed as a rounded relation of the real width and the value of heigh.
760 return Math.round( $.clientWidth / natural.width * natural.height ) == $.clientHeight ||
761 Math.round( $.clientHeight / natural.height * natural.width ) == $.clientWidth;
762 },
763
764 /**
765 * Returns natural dimensions of the image. For modern browsers
766 * it uses natural(Width|Height). For old ones (IE8) it creates
767 * a new image and reads the dimensions.
768 *
769 * @param {CKEDITOR.dom.element} image
770 * @returns {Object}
771 */
772 getNatural: function( image ) {
773 var dimensions;
774
775 if ( image.$.naturalWidth ) {
776 dimensions = {
777 width: image.$.naturalWidth,
778 height: image.$.naturalHeight
779 };
780 } else {
781 var img = new Image();
782 img.src = image.getAttribute( 'src' );
783
784 dimensions = {
785 width: img.width,
786 height: img.height
787 };
788 }
789
790 return dimensions;
791 },
792
793 /**
794 * Returns an attribute getter function. Default getter comes from the Link plugin
795 * and is documented by {@link CKEDITOR.plugins.link#getLinkAttributes}.
796 *
797 * **Note:** It is possible to override this method and use a custom getter e.g.
798 * in the absence of the Link plugin.
799 *
800 * **Note:** If a custom getter is used, a data model format it produces
801 * must be compatible with {@link CKEDITOR.plugins.link#getLinkAttributes}.
802 *
803 * **Note:** A custom getter must understand the data model format produced by
804 * {@link #getLinkAttributesParser} to work correctly.
805 *
806 * @returns {Function} A function that gets (composes) link attributes.
807 * @since 4.5.5
808 */
809 getLinkAttributesGetter: function() {
810 // http://dev.ckeditor.com/ticket/13885
811 return CKEDITOR.plugins.link.getLinkAttributes;
812 },
813
814 /**
815 * Returns an attribute parser function. Default parser comes from the Link plugin
816 * and is documented by {@link CKEDITOR.plugins.link#parseLinkAttributes}.
817 *
818 * **Note:** It is possible to override this method and use a custom parser e.g.
819 * in the absence of the Link plugin.
820 *
821 * **Note:** If a custom parser is used, a data model format produced by the parser
822 * must be compatible with {@link #getLinkAttributesGetter}.
823 *
824 * **Note:** If a custom parser is used, it should be compatible with the
825 * {@link CKEDITOR.plugins.link#parseLinkAttributes} data model format. Otherwise the
826 * Link plugin dialog may not be populated correctly with parsed data. However
827 * as long as Enhanced Image is **not** used with the Link plugin dialog, any custom data model
828 * will work, being stored as an internal property of Enhanced Image widget's data only.
829 *
830 * @returns {Function} A function that parses attributes.
831 * @since 4.5.5
832 */
833 getLinkAttributesParser: function() {
834 // http://dev.ckeditor.com/ticket/13885
835 return CKEDITOR.plugins.link.parseLinkAttributes;
836 }
837 };
838
839 function setWrapperAlign( widget, alignClasses ) {
840 var wrapper = widget.wrapper,
841 align = widget.data.align,
842 hasCaption = widget.data.hasCaption;
843
844 if ( alignClasses ) {
845 // Remove all align classes first.
846 for ( var i = 3; i--; )
847 wrapper.removeClass( alignClasses[ i ] );
848
849 if ( align == 'center' ) {
850 // Avoid touching non-captioned, centered widgets because
851 // they have the class set on the element instead of wrapper:
852 //
853 // <div class="cke_widget_wrapper">
854 // <p class="center-class">
855 // <img />
856 // </p>
857 // </div>
858 if ( hasCaption ) {
859 wrapper.addClass( alignClasses[ 1 ] );
860 }
861 } else if ( align != 'none' ) {
862 wrapper.addClass( alignClasses[ alignmentsObj[ align ] ] );
863 }
864 } else {
865 if ( align == 'center' ) {
866 if ( hasCaption )
867 wrapper.setStyle( 'text-align', 'center' );
868 else
869 wrapper.removeStyle( 'text-align' );
870
871 wrapper.removeStyle( 'float' );
872 }
873 else {
874 if ( align == 'none' )
875 wrapper.removeStyle( 'float' );
876 else
877 wrapper.setStyle( 'float', align );
878
879 wrapper.removeStyle( 'text-align' );
880 }
881 }
882 }
883
884 // Returns a function that creates widgets from all <img> and
885 // <figure class="{config.image2_captionedClass}"> elements.
886 //
887 // @param {CKEDITOR.editor} editor
888 // @returns {Function}
889 function upcastWidgetElement( editor ) {
890 var isCenterWrapper = centerWrapperChecker( editor ),
891 captionedClass = editor.config.image2_captionedClass;
892
893 // @param {CKEDITOR.htmlParser.element} el
894 // @param {Object} data
895 return function( el, data ) {
896 var dimensions = { width: 1, height: 1 },
897 name = el.name,
898 image;
899
900 // http://dev.ckeditor.com/ticket/11110 Don't initialize on pasted fake objects.
901 if ( el.attributes[ 'data-cke-realelement' ] )
902 return;
903
904 // If a center wrapper is found, there are 3 possible cases:
905 //
906 // 1. <div style="text-align:center"><figure>...</figure></div>.
907 // In this case centering is done with a class set on widget.wrapper.
908 // Simply replace centering wrapper with figure (it's no longer necessary).
909 //
910 // 2. <p style="text-align:center"><img/></p>.
911 // Nothing to do here: <p> remains for styling purposes.
912 //
913 // 3. <div style="text-align:center"><img/></div>.
914 // Nothing to do here (2.) but that case is only possible in enterMode different
915 // than ENTER_P.
916 if ( isCenterWrapper( el ) ) {
917 if ( name == 'div' ) {
918 var figure = el.getFirst( 'figure' );
919
920 // Case #1.
921 if ( figure ) {
922 el.replaceWith( figure );
923 el = figure;
924 }
925 }
926 // Cases #2 and #3 (handled transparently)
927
928 // If there's a centering wrapper, save it in data.
929 data.align = 'center';
930
931 // Image can be wrapped in link <a><img/></a>.
932 image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
933 }
934
935 // No center wrapper has been found.
936 else if ( name == 'figure' && el.hasClass( captionedClass ) ) {
937 image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
938
939 // Upcast linked image like <a><img/></a>.
940 } else if ( isLinkedOrStandaloneImage( el ) ) {
941 image = el.name == 'a' ? el.children[ 0 ] : el;
942 }
943
944 if ( !image )
945 return;
946
947 // If there's an image, then cool, we got a widget.
948 // Now just remove dimension attributes expressed with %.
949 for ( var d in dimensions ) {
950 var dimension = image.attributes[ d ];
951
952 if ( dimension && dimension.match( regexPercent ) )
953 delete image.attributes[ d ];
954 }
955
956 return el;
957 };
958 }
959
960 // Returns a function which transforms the widget to the external format
961 // according to the current configuration.
962 //
963 // @param {CKEDITOR.editor}
964 function downcastWidgetElement( editor ) {
965 var alignClasses = editor.config.image2_alignClasses;
966
967 // @param {CKEDITOR.htmlParser.element} el
968 return function( el ) {
969 // In case of <a><img/></a>, <img/> is the element to hold
970 // inline styles or classes (image2_alignClasses).
971 var attrsHolder = el.name == 'a' ? el.getFirst() : el,
972 attrs = attrsHolder.attributes,
973 align = this.data.align;
974
975 // De-wrap the image from resize handle wrapper.
976 // Only block widgets have one.
977 if ( !this.inline ) {
978 var resizeWrapper = el.getFirst( 'span' );
979
980 if ( resizeWrapper )
981 resizeWrapper.replaceWith( resizeWrapper.getFirst( { img: 1, a: 1 } ) );
982 }
983
984 if ( align && align != 'none' ) {
985 var styles = CKEDITOR.tools.parseCssText( attrs.style || '' );
986
987 // When the widget is captioned (<figure>) and internally centering is done
988 // with widget's wrapper style/class, in the external data representation,
989 // <figure> must be wrapped with an element holding an style/class:
990 //
991 // <div style="text-align:center">
992 // <figure class="image" style="display:inline-block">...</figure>
993 // </div>
994 // or
995 // <div class="some-center-class">
996 // <figure class="image">...</figure>
997 // </div>
998 //
999 if ( align == 'center' && el.name == 'figure' ) {
1000 el = el.wrapWith( new CKEDITOR.htmlParser.element( 'div',
1001 alignClasses ? { 'class': alignClasses[ 1 ] } : { style: 'text-align:center' } ) );
1002 }
1003
1004 // If left/right, add float style to the downcasted element.
1005 else if ( align in { left: 1, right: 1 } ) {
1006 if ( alignClasses )
1007 attrsHolder.addClass( alignClasses[ alignmentsObj[ align ] ] );
1008 else
1009 styles[ 'float' ] = align;
1010 }
1011
1012 // Update element styles.
1013 if ( !alignClasses && !CKEDITOR.tools.isEmpty( styles ) )
1014 attrs.style = CKEDITOR.tools.writeCssText( styles );
1015 }
1016
1017 return el;
1018 };
1019 }
1020
1021 // Returns a function that checks if an element is a centering wrapper.
1022 //
1023 // @param {CKEDITOR.editor} editor
1024 // @returns {Function}
1025 function centerWrapperChecker( editor ) {
1026 var captionedClass = editor.config.image2_captionedClass,
1027 alignClasses = editor.config.image2_alignClasses,
1028 validChildren = { figure: 1, a: 1, img: 1 };
1029
1030 return function( el ) {
1031 // Wrapper must be either <div> or <p>.
1032 if ( !( el.name in { div: 1, p: 1 } ) )
1033 return false;
1034
1035 var children = el.children;
1036
1037 // Centering wrapper can have only one child.
1038 if ( children.length !== 1 )
1039 return false;
1040
1041 var child = children[ 0 ];
1042
1043 // Only <figure> or <img /> can be first (only) child of centering wrapper,
1044 // regardless of its type.
1045 if ( !( child.name in validChildren ) )
1046 return false;
1047
1048 // If centering wrapper is <p>, only <img /> can be the child.
1049 // <p style="text-align:center"><img /></p>
1050 if ( el.name == 'p' ) {
1051 if ( !isLinkedOrStandaloneImage( child ) )
1052 return false;
1053 }
1054 // Centering <div> can hold <img/> or <figure>, depending on enterMode.
1055 else {
1056 // If a <figure> is the first (only) child, it must have a class.
1057 // <div style="text-align:center"><figure>...</figure><div>
1058 if ( child.name == 'figure' ) {
1059 if ( !child.hasClass( captionedClass ) )
1060 return false;
1061 } else {
1062 // Centering <div> can hold <img/> or <a><img/></a> only when enterMode
1063 // is ENTER_(BR|DIV).
1064 // <div style="text-align:center"><img /></div>
1065 // <div style="text-align:center"><a><img /></a></div>
1066 if ( editor.enterMode == CKEDITOR.ENTER_P )
1067 return false;
1068
1069 // Regardless of enterMode, a child which is not <figure> must be
1070 // either <img/> or <a><img/></a>.
1071 if ( !isLinkedOrStandaloneImage( child ) )
1072 return false;
1073 }
1074 }
1075
1076 // Centering wrapper got to be... centering. If image2_alignClasses are defined,
1077 // check for centering class. Otherwise, check the style.
1078 if ( alignClasses ? el.hasClass( alignClasses[ 1 ] ) :
1079 CKEDITOR.tools.parseCssText( el.attributes.style || '', true )[ 'text-align' ] == 'center' )
1080 return true;
1081
1082 return false;
1083 };
1084 }
1085
1086 // Checks whether element is <img/> or <a><img/></a>.
1087 //
1088 // @param {CKEDITOR.htmlParser.element}
1089 function isLinkedOrStandaloneImage( el ) {
1090 if ( el.name == 'img' )
1091 return true;
1092 else if ( el.name == 'a' )
1093 return el.children.length == 1 && el.getFirst( 'img' );
1094
1095 return false;
1096 }
1097
1098 // Sets width and height of the widget image according to current widget data.
1099 //
1100 // @param {CKEDITOR.plugins.widget} widget
1101 function setDimensions( widget ) {
1102 var data = widget.data,
1103 dimensions = { width: data.width, height: data.height },
1104 image = widget.parts.image;
1105
1106 for ( var d in dimensions ) {
1107 if ( dimensions[ d ] )
1108 image.setAttribute( d, dimensions[ d ] );
1109 else
1110 image.removeAttribute( d );
1111 }
1112 }
1113
1114 // Defines all features related to drag-driven image resizing.
1115 //
1116 // @param {CKEDITOR.plugins.widget} widget
1117 function setupResizer( widget ) {
1118 var editor = widget.editor,
1119 editable = editor.editable(),
1120 doc = editor.document,
1121
1122 // Store the resizer in a widget for testing (http://dev.ckeditor.com/ticket/11004).
1123 resizer = widget.resizer = doc.createElement( 'span' );
1124
1125 resizer.addClass( 'cke_image_resizer' );
1126 resizer.setAttribute( 'title', editor.lang.image2.resizer );
1127 resizer.append( new CKEDITOR.dom.text( '\u200b', doc ) );
1128
1129 // Inline widgets don't need a resizer wrapper as an image spans the entire widget.
1130 if ( !widget.inline ) {
1131 var imageOrLink = widget.parts.link || widget.parts.image,
1132 oldResizeWrapper = imageOrLink.getParent(),
1133 resizeWrapper = doc.createElement( 'span' );
1134
1135 resizeWrapper.addClass( 'cke_image_resizer_wrapper' );
1136 resizeWrapper.append( imageOrLink );
1137 resizeWrapper.append( resizer );
1138 widget.element.append( resizeWrapper, true );
1139
1140 // Remove the old wrapper which could came from e.g. pasted HTML
1141 // and which could be corrupted (e.g. resizer span has been lost).
1142 if ( oldResizeWrapper.is( 'span' ) )
1143 oldResizeWrapper.remove();
1144 } else {
1145 widget.wrapper.append( resizer );
1146 }
1147
1148 // Calculate values of size variables and mouse offsets.
1149 resizer.on( 'mousedown', function( evt ) {
1150 var image = widget.parts.image,
1151
1152 // "factor" can be either 1 or -1. I.e.: For right-aligned images, we need to
1153 // subtract the difference to get proper width, etc. Without "factor",
1154 // resizer starts working the opposite way.
1155 factor = widget.data.align == 'right' ? -1 : 1,
1156
1157 // The x-coordinate of the mouse relative to the screen
1158 // when button gets pressed.
1159 startX = evt.data.$.screenX,
1160 startY = evt.data.$.screenY,
1161
1162 // The initial dimensions and aspect ratio of the image.
1163 startWidth = image.$.clientWidth,
1164 startHeight = image.$.clientHeight,
1165 ratio = startWidth / startHeight,
1166
1167 listeners = [],
1168
1169 // A class applied to editable during resizing.
1170 cursorClass = 'cke_image_s' + ( !~factor ? 'w' : 'e' ),
1171
1172 nativeEvt, newWidth, newHeight, updateData,
1173 moveDiffX, moveDiffY, moveRatio;
1174
1175 // Save the undo snapshot first: before resizing.
1176 editor.fire( 'saveSnapshot' );
1177
1178 // Mousemove listeners are removed on mouseup.
1179 attachToDocuments( 'mousemove', onMouseMove, listeners );
1180
1181 // Clean up the mousemove listener. Update widget data if valid.
1182 attachToDocuments( 'mouseup', onMouseUp, listeners );
1183
1184 // The entire editable will have the special cursor while resizing goes on.
1185 editable.addClass( cursorClass );
1186
1187 // This is to always keep the resizer element visible while resizing.
1188 resizer.addClass( 'cke_image_resizing' );
1189
1190 // Attaches an event to a global document if inline editor.
1191 // Additionally, if classic (`iframe`-based) editor, also attaches the same event to `iframe`'s document.
1192 function attachToDocuments( name, callback, collection ) {
1193 var globalDoc = CKEDITOR.document,
1194 listeners = [];
1195
1196 if ( !doc.equals( globalDoc ) )
1197 listeners.push( globalDoc.on( name, callback ) );
1198
1199 listeners.push( doc.on( name, callback ) );
1200
1201 if ( collection ) {
1202 for ( var i = listeners.length; i--; )
1203 collection.push( listeners.pop() );
1204 }
1205 }
1206
1207 // Calculate with first, and then adjust height, preserving ratio.
1208 function adjustToX() {
1209 newWidth = startWidth + factor * moveDiffX;
1210 newHeight = Math.round( newWidth / ratio );
1211 }
1212
1213 // Calculate height first, and then adjust width, preserving ratio.
1214 function adjustToY() {
1215 newHeight = startHeight - moveDiffY;
1216 newWidth = Math.round( newHeight * ratio );
1217 }
1218
1219 // This is how variables refer to the geometry.
1220 // Note: x corresponds to moveOffset, this is the position of mouse
1221 // Note: o corresponds to [startX, startY].
1222 //
1223 // +--------------+--------------+
1224 // | | |
1225 // | I | II |
1226 // | | |
1227 // +------------- o -------------+ _ _ _
1228 // | | | ^
1229 // | VI | III | | moveDiffY
1230 // | | x _ _ _ _ _ v
1231 // +--------------+---------|----+
1232 // | |
1233 // <------->
1234 // moveDiffX
1235 function onMouseMove( evt ) {
1236 nativeEvt = evt.data.$;
1237
1238 // This is how far the mouse is from the point the button was pressed.
1239 moveDiffX = nativeEvt.screenX - startX;
1240 moveDiffY = startY - nativeEvt.screenY;
1241
1242 // This is the aspect ratio of the move difference.
1243 moveRatio = Math.abs( moveDiffX / moveDiffY );
1244
1245 // Left, center or none-aligned widget.
1246 if ( factor == 1 ) {
1247 if ( moveDiffX <= 0 ) {
1248 // Case: IV.
1249 if ( moveDiffY <= 0 )
1250 adjustToX();
1251
1252 // Case: I.
1253 else {
1254 if ( moveRatio >= ratio )
1255 adjustToX();
1256 else
1257 adjustToY();
1258 }
1259 } else {
1260 // Case: III.
1261 if ( moveDiffY <= 0 ) {
1262 if ( moveRatio >= ratio )
1263 adjustToY();
1264 else
1265 adjustToX();
1266 }
1267
1268 // Case: II.
1269 else {
1270 adjustToY();
1271 }
1272 }
1273 }
1274
1275 // Right-aligned widget. It mirrors behaviours, so I becomes II,
1276 // IV becomes III and vice-versa.
1277 else {
1278 if ( moveDiffX <= 0 ) {
1279 // Case: IV.
1280 if ( moveDiffY <= 0 ) {
1281 if ( moveRatio >= ratio )
1282 adjustToY();
1283 else
1284 adjustToX();
1285 }
1286
1287 // Case: I.
1288 else {
1289 adjustToY();
1290 }
1291 } else {
1292 // Case: III.
1293 if ( moveDiffY <= 0 )
1294 adjustToX();
1295
1296 // Case: II.
1297 else {
1298 if ( moveRatio >= ratio ) {
1299 adjustToX();
1300 } else {
1301 adjustToY();
1302 }
1303 }
1304 }
1305 }
1306
1307 // Don't update attributes if less than 10.
1308 // This is to prevent images to visually disappear.
1309 if ( newWidth >= 15 && newHeight >= 15 ) {
1310 image.setAttributes( { width: newWidth, height: newHeight } );
1311 updateData = true;
1312 } else {
1313 updateData = false;
1314 }
1315 }
1316
1317 function onMouseUp() {
1318 var l;
1319
1320 while ( ( l = listeners.pop() ) )
1321 l.removeListener();
1322
1323 // Restore default cursor by removing special class.
1324 editable.removeClass( cursorClass );
1325
1326 // This is to bring back the regular behaviour of the resizer.
1327 resizer.removeClass( 'cke_image_resizing' );
1328
1329 if ( updateData ) {
1330 widget.setData( { width: newWidth, height: newHeight } );
1331
1332 // Save another undo snapshot: after resizing.
1333 editor.fire( 'saveSnapshot' );
1334 }
1335
1336 // Don't update data twice or more.
1337 updateData = false;
1338 }
1339 } );
1340
1341 // Change the position of the widget resizer when data changes.
1342 widget.on( 'data', function() {
1343 resizer[ widget.data.align == 'right' ? 'addClass' : 'removeClass' ]( 'cke_image_resizer_left' );
1344 } );
1345 }
1346
1347 // Integrates widget alignment setting with justify
1348 // plugin's commands (execution and refreshment).
1349 // @param {CKEDITOR.editor} editor
1350 // @param {String} value 'left', 'right', 'center' or 'block'
1351 function alignCommandIntegrator( editor ) {
1352 var execCallbacks = [],
1353 enabled;
1354
1355 return function( value ) {
1356 var command = editor.getCommand( 'justify' + value );
1357
1358 // Most likely, the justify plugin isn't loaded.
1359 if ( !command )
1360 return;
1361
1362 // This command will be manually refreshed along with
1363 // other commands after exec.
1364 execCallbacks.push( function() {
1365 command.refresh( editor, editor.elementPath() );
1366 } );
1367
1368 if ( value in { right: 1, left: 1, center: 1 } ) {
1369 command.on( 'exec', function( evt ) {
1370 var widget = getFocusedWidget( editor );
1371
1372 if ( widget ) {
1373 widget.setData( 'align', value );
1374
1375 // Once the widget changed its align, all the align commands
1376 // must be refreshed: the event is to be cancelled.
1377 for ( var i = execCallbacks.length; i--; )
1378 execCallbacks[ i ]();
1379
1380 evt.cancel();
1381 }
1382 } );
1383 }
1384
1385 command.on( 'refresh', function( evt ) {
1386 var widget = getFocusedWidget( editor ),
1387 allowed = { right: 1, left: 1, center: 1 };
1388
1389 if ( !widget )
1390 return;
1391
1392 // Cache "enabled" on first use. This is because filter#checkFeature may
1393 // not be available during plugin's afterInit in the future — a moment when
1394 // alignCommandIntegrator is called.
1395 if ( enabled === undefined )
1396 enabled = editor.filter.checkFeature( editor.widgets.registered.image.features.align );
1397
1398 // Don't allow justify commands when widget alignment is disabled (http://dev.ckeditor.com/ticket/11004).
1399 if ( !enabled )
1400 this.setState( CKEDITOR.TRISTATE_DISABLED );
1401 else {
1402 this.setState(
1403 ( widget.data.align == value ) ? (
1404 CKEDITOR.TRISTATE_ON
1405 ) : (
1406 ( value in allowed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED
1407 )
1408 );
1409 }
1410
1411 evt.cancel();
1412 } );
1413 };
1414 }
1415
1416 function linkCommandIntegrator( editor ) {
1417 // Nothing to integrate with if link is not loaded.
1418 if ( !editor.plugins.link )
1419 return;
1420
1421 CKEDITOR.on( 'dialogDefinition', function( evt ) {
1422 var dialog = evt.data;
1423
1424 if ( dialog.name == 'link' ) {
1425 var def = dialog.definition;
1426
1427 var onShow = def.onShow,
1428 onOk = def.onOk;
1429
1430 def.onShow = function() {
1431 var widget = getFocusedWidget( editor ),
1432 displayTextField = this.getContentElement( 'info', 'linkDisplayText' ).getElement().getParent().getParent();
1433
1434 // Widget cannot be enclosed in a link, i.e.
1435 // <a>foo<inline widget/>bar</a>
1436 if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
1437 this.setupContent( widget.data.link || {} );
1438
1439 // Hide the display text in case of linking image2 widget.
1440 displayTextField.hide();
1441 } else {
1442 // Make sure that display text is visible, as it might be hidden by image2 integration
1443 // before.
1444 displayTextField.show();
1445 onShow.apply( this, arguments );
1446 }
1447 };
1448
1449 // Set widget data if linking the widget using
1450 // link dialog (instead of default action).
1451 // State shifter handles data change and takes
1452 // care of internal DOM structure of linked widget.
1453 def.onOk = function() {
1454 var widget = getFocusedWidget( editor );
1455
1456 // Widget cannot be enclosed in a link, i.e.
1457 // <a>foo<inline widget/>bar</a>
1458 if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
1459 var data = {};
1460
1461 // Collect data from fields.
1462 this.commitContent( data );
1463
1464 // Set collected data to widget.
1465 widget.setData( 'link', data );
1466 } else {
1467 onOk.apply( this, arguments );
1468 }
1469 };
1470 }
1471 } );
1472
1473 // Overwrite default behaviour of unlink command.
1474 editor.getCommand( 'unlink' ).on( 'exec', function( evt ) {
1475 var widget = getFocusedWidget( editor );
1476
1477 // Override unlink only when link truly belongs to the widget.
1478 // If wrapped inline widget in a link, let default unlink work (http://dev.ckeditor.com/ticket/11814).
1479 if ( !widget || !widget.parts.link )
1480 return;
1481
1482 widget.setData( 'link', null );
1483
1484 // Selection (which is fake) may not change if unlinked image in focused widget,
1485 // i.e. if captioned image. Let's refresh command state manually here.
1486 this.refresh( editor, editor.elementPath() );
1487
1488 evt.cancel();
1489 } );
1490
1491 // Overwrite default refresh of unlink command.
1492 editor.getCommand( 'unlink' ).on( 'refresh', function( evt ) {
1493 var widget = getFocusedWidget( editor );
1494
1495 if ( !widget )
1496 return;
1497
1498 // Note that widget may be wrapped in a link, which
1499 // does not belong to that widget (http://dev.ckeditor.com/ticket/11814).
1500 this.setState( widget.data.link || widget.wrapper.getAscendant( 'a' ) ?
1501 CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
1502
1503 evt.cancel();
1504 } );
1505 }
1506
1507 // Returns the focused widget, if of the type specific for this plugin.
1508 // If no widget is focused, `null` is returned.
1509 //
1510 // @param {CKEDITOR.editor}
1511 // @returns {CKEDITOR.plugins.widget}
1512 function getFocusedWidget( editor ) {
1513 var widget = editor.widgets.focused;
1514
1515 if ( widget && widget.name == 'image' )
1516 return widget;
1517
1518 return null;
1519 }
1520
1521 // Returns a set of widget allowedContent rules, depending
1522 // on configurations like config#image2_alignClasses or
1523 // config#image2_captionedClass.
1524 //
1525 // @param {CKEDITOR.editor}
1526 // @returns {Object}
1527 function getWidgetAllowedContent( editor ) {
1528 var alignClasses = editor.config.image2_alignClasses,
1529 rules = {
1530 // Widget may need <div> or <p> centering wrapper.
1531 div: {
1532 match: centerWrapperChecker( editor )
1533 },
1534 p: {
1535 match: centerWrapperChecker( editor )
1536 },
1537 img: {
1538 attributes: '!src,alt,width,height'
1539 },
1540 figure: {
1541 classes: '!' + editor.config.image2_captionedClass
1542 },
1543 figcaption: true
1544 };
1545
1546 if ( alignClasses ) {
1547 // Centering class from the config.
1548 rules.div.classes = alignClasses[ 1 ];
1549 rules.p.classes = rules.div.classes;
1550
1551 // Left/right classes from the config.
1552 rules.img.classes = alignClasses[ 0 ] + ',' + alignClasses[ 2 ];
1553 rules.figure.classes += ',' + rules.img.classes;
1554 } else {
1555 // Centering with text-align.
1556 rules.div.styles = 'text-align';
1557 rules.p.styles = 'text-align';
1558
1559 rules.img.styles = 'float';
1560 rules.figure.styles = 'float,display';
1561 }
1562
1563 return rules;
1564 }
1565
1566 // Returns a set of widget feature rules, depending
1567 // on editor configuration. Note that the following may not cover
1568 // all the possible cases since requiredContent supports a single
1569 // tag only.
1570 //
1571 // @param {CKEDITOR.editor}
1572 // @returns {Object}
1573 function getWidgetFeatures( editor ) {
1574 var alignClasses = editor.config.image2_alignClasses,
1575 features = {
1576 dimension: {
1577 requiredContent: 'img[width,height]'
1578 },
1579 align: {
1580 requiredContent: 'img' +
1581 ( alignClasses ? '(' + alignClasses[ 0 ] + ')' : '{float}' )
1582 },
1583 caption: {
1584 requiredContent: 'figcaption'
1585 }
1586 };
1587
1588 return features;
1589 }
1590
1591 // Returns element which is styled, considering current
1592 // state of the widget.
1593 //
1594 // @see CKEDITOR.plugins.widget#applyStyle
1595 // @param {CKEDITOR.plugins.widget} widget
1596 // @returns {CKEDITOR.dom.element}
1597 function getStyleableElement( widget ) {
1598 return widget.data.hasCaption ? widget.element : widget.parts.image;
1599 }
1600} )();
1601
1602/**
1603 * A CSS class applied to the `<figure>` element of a captioned image.
1604 *
1605 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1606 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1607 *
1608 * // Changes the class to "captionedImage".
1609 * config.image2_captionedClass = 'captionedImage';
1610 *
1611 * @cfg {String} [image2_captionedClass='image']
1612 * @member CKEDITOR.config
1613 */
1614CKEDITOR.config.image2_captionedClass = 'image';
1615
1616/**
1617 * Determines whether dimension inputs should be automatically filled when the image URL changes in the Enhanced Image
1618 * plugin dialog window.
1619 *
1620 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1621 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1622 *
1623 * config.image2_prefillDimensions = false;
1624 *
1625 * @since 4.5
1626 * @cfg {Boolean} [image2_prefillDimensions=true]
1627 * @member CKEDITOR.config
1628 */
1629
1630/**
1631 * Disables the image resizer. By default the resizer is enabled.
1632 *
1633 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1634 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1635 *
1636 * config.image2_disableResizer = true;
1637 *
1638 * @since 4.5
1639 * @cfg {Boolean} [image2_disableResizer=false]
1640 * @member CKEDITOR.config
1641 */
1642
1643/**
1644 * CSS classes applied to aligned images. Useful to take control over the way
1645 * the images are aligned, i.e. to customize output HTML and integrate external stylesheets.
1646 *
1647 * Classes should be defined in an array of three elements, containing left, center, and right
1648 * alignment classes, respectively. For example:
1649 *
1650 * config.image2_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
1651 *
1652 * **Note**: Once this configuration option is set, the plugin will no longer produce inline
1653 * styles for alignment. It means that e.g. the following HTML will be produced:
1654 *
1655 * <img alt="My image" class="custom-center-class" src="foo.png" />
1656 *
1657 * instead of:
1658 *
1659 * <img alt="My image" style="float:left" src="foo.png" />
1660 *
1661 * **Note**: Once this configuration option is set, corresponding style definitions
1662 * must be supplied to the editor:
1663 *
1664 * * For [classic editor](#!/guide/dev_framed) it can be done by defining additional
1665 * styles in the {@link CKEDITOR.config#contentsCss stylesheets loaded by the editor}. The same
1666 * styles must be provided on the target page where the content will be loaded.
1667 * * For [inline editor](#!/guide/dev_inline) the styles can be defined directly
1668 * with `<style> ... <style>` or `<link href="..." rel="stylesheet">`, i.e. within the `<head>`
1669 * of the page.
1670 *
1671 * For example, considering the following configuration:
1672 *
1673 * config.image2_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
1674 *
1675 * CSS rules can be defined as follows:
1676 *
1677 * .align-left {
1678 * float: left;
1679 * }
1680 *
1681 * .align-right {
1682 * float: right;
1683 * }
1684 *
1685 * .align-center {
1686 * text-align: center;
1687 * }
1688 *
1689 * .align-center > figure {
1690 * display: inline-block;
1691 * }
1692 *
1693 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1694 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1695 *
1696 * @since 4.4
1697 * @cfg {String[]} [image2_alignClasses=null]
1698 * @member CKEDITOR.config
1699 */
1700
1701/**
1702 * Determines whether alternative text is required for the captioned image.
1703 *
1704 * config.image2_altRequired = true;
1705 *
1706 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1707 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1708 *
1709 * @since 4.6.0
1710 * @cfg {Boolean} [image2_altRequired=false]
1711 * @member CKEDITOR.config
1712 */
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/samples/assets/image1.jpg b/app/assets/javascripts/ckeditor/plugins/image2/samples/assets/image1.jpg new file mode 100644 index 0000000..ca491e3 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/samples/assets/image1.jpg
Binary files differ
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/samples/assets/image2.jpg b/app/assets/javascripts/ckeditor/plugins/image2/samples/assets/image2.jpg new file mode 100644 index 0000000..3dd6d61 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/samples/assets/image2.jpg
Binary files differ
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/samples/image2.html b/app/assets/javascripts/ckeditor/plugins/image2/samples/image2.html new file mode 100644 index 0000000..9c3ef13 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/image2/samples/image2.html
@@ -0,0 +1,68 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
4For licensing, see LICENSE.md or http://ckeditor.com/license
5-->
6<html>
7<head>
8 <meta charset="utf-8">
9 <title>New Image plugin &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <script>
12 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
13 CKEDITOR.tools.enableHtml5Elements( document );
14 </script>
15 <link href="../../../samples/old/sample.css" rel="stylesheet">
16 <meta name="ckeditor-sample-name" content="New Image plugin">
17 <meta name="ckeditor-sample-group" content="Plugins">
18 <meta name="ckeditor-sample-description" content="Using the new Image plugin to insert captioned images and adjust their dimensions.">
19 <meta name="ckeditor-sample-isnew" content="1">
20</head>
21<body>
22 <h1 class="samples">
23 <a href="../../../samples/old/index.html">CKEditor Samples</a> &raquo; New Image plugin
24 </h1>
25 <div class="warning deprecated">
26 This sample is not maintained anymore. Check out its <a href="http://sdk.ckeditor.com/samples/captionedimage.html">brand new version in CKEditor SDK</a>.
27 </div>
28
29 <div class="description">
30 <p>
31 This editor is using the new <strong>Image</strong> (<code>image2</code>) plugin, which implements a dynamic <em>click-and-drag</em> resizing
32 and easy captioning of the images.
33 </p>
34 <p>
35 To use the new plugin, extend <code><a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-extraPlugins">config.extraPlugins</a></code>:
36 </p>
37<pre class="samples">
38CKEDITOR.replace( '<em>textarea_id</em>', {
39 <strong>extraPlugins: 'image2'</strong>
40} );
41</pre>
42 </div>
43
44 <textarea id="editor1" cols="10" rows="10">
45 &lt;h1&gt;Apollo 11&lt;/h1&gt;&lt;figure class=&quot;image&quot; style=&quot;float: right&quot;&gt;&lt;img alt=&quot;Saturn V&quot; src=&quot;assets/image1.jpg&quot; width=&quot;200&quot; /&gt;&lt;figcaption&gt;Roll out of Saturn V on launch pad&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;Apollo 11&lt;/strong&gt; was the spaceflight that landed the first humans, Americans &lt;a href=&quot;http://en.wikipedia.org/wiki/Neil_Armstrong&quot; title=&quot;Neil Armstrong&quot;&gt;Neil Armstrong&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Buzz_Aldrin&quot; title=&quot;Buzz Aldrin&quot;&gt;Buzz Aldrin&lt;/a&gt;, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.&lt;/p&gt;&lt;p&gt;Armstrong spent about &lt;s&gt;three and a half&lt;/s&gt; two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&amp;nbsp;kg) of lunar material for return to Earth. A third member of the mission, &lt;a href=&quot;http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)&quot; title=&quot;Michael Collins (astronaut)&quot;&gt;Michael Collins&lt;/a&gt;, piloted the &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_Command/Service_Module&quot; title=&quot;Apollo Command/Service Module&quot;&gt;command&lt;/a&gt; spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.&lt;/p&gt;&lt;h2&gt;Broadcasting and &lt;em&gt;quotes&lt;/em&gt; &lt;a id=&quot;quotes&quot; name=&quot;quotes&quot;&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;One small step for [a] man, one giant leap for mankind.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Apollo 11 effectively ended the &lt;a href=&quot;http://en.wikipedia.org/wiki/Space_Race&quot; title=&quot;Space Race&quot;&gt;Space Race&lt;/a&gt; and fulfilled a national goal proposed in 1961 by the late U.S. President &lt;a href=&quot;http://en.wikipedia.org/wiki/John_F._Kennedy&quot; title=&quot;John F. Kennedy&quot;&gt;John F. Kennedy&lt;/a&gt; in a speech before the United States Congress:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.&lt;/p&gt;&lt;/blockquote&gt;&lt;figure class=&quot;image&quot; style=&quot;float: right&quot;&gt;&lt;img alt=&quot;The Eagle&quot; src=&quot;assets/image2.jpg&quot; style=&quot;width: 200px&quot; /&gt;&lt;figcaption&gt;The Eagle in lunar orbit&lt;/figcaption&gt;&lt;/figure&gt;&lt;h2&gt;Technical details &lt;a id=&quot;tech-details&quot; name=&quot;tech-details&quot;&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href=&quot;http://en.wikipedia.org/wiki/Kennedy_Space_Center&quot; title=&quot;Kennedy Space Center&quot;&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href=&quot;http://en.wikipedia.org/wiki/NASA&quot; title=&quot;NASA&quot;&gt;NASA&lt;/a&gt;&amp;#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;After being sent to the Moon by the Saturn V&amp;#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mare_Tranquillitatis&quot; title=&quot;Mare Tranquillitatis&quot;&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Pacific_Ocean&quot; title=&quot;Pacific Ocean&quot;&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt;&lt;hr /&gt;&lt;p style=&quot;text-align:right&quot;&gt;&lt;small&gt;Source: &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_11&quot;&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
46 </textarea>
47
48 <script>
49
50 CKEDITOR.replace( 'editor1', {
51 extraPlugins: 'image2',
52 height: 450
53 } );
54
55 </script>
56
57 <div id="footer">
58 <hr>
59 <p>
60 CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
61 </p>
62 <p id="copy">
63 Copyright &copy; 2003-2017, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
64 Knabben. All rights reserved.
65 </p>
66 </div>
67</body>
68</html>
diff --git a/app/assets/javascripts/ckeditor/plugins/lineutils/dev/dnd.html b/app/assets/javascripts/ckeditor/plugins/lineutils/dev/dnd.html new file mode 100644 index 0000000..971d9cd --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/lineutils/dev/dnd.html
@@ -0,0 +1,172 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
4For licensing, see LICENSE.md or http://ckeditor.com/license
5-->
6<html>
7<head>
8 <meta charset="utf-8">
9 <title>Widget Drag &amp; Drop with Lineutils &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <script>
12 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
13 CKEDITOR.tools.enableHtml5Elements( document );
14 </script>
15 <link href="../../../samples/old/sample.css" rel="stylesheet">
16 <link href="../../image2/samples/contents.css" rel="stylesheet">
17</head>
18<body>
19 <h1 class="samples">
20 <a href="../../../samples/old/index.html">CKEditor Samples</a> &raquo; Widget Drag &amp; Drop with Lineutils
21 </h1>
22
23 <h3>Classic (iframe-based) Editor</h3>
24
25 <textarea id="editor1" cols="10" rows="10">
26 <h1>Apollo 11</h1>
27
28 <figure class="caption" style="float:right"><img alt="Saturn V" src="../../image2/samples/assets/image1.jpg" width="200" />
29 <figcaption>Roll out of Saturn V on launch pad</figcaption>
30 </figure>
31
32 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.</p>
33
34 <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
35
36 <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
37
38 <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
39
40 <blockquote>
41 <p>One small step for [a] man, one giant leap for mankind.</p>
42 </blockquote>
43
44 <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p>
45
46 <div style="text-align:center">
47 <figure class="caption" style="display:inline-block"><img alt="The Eagle" height="123" src="../../image2/samples/assets/image2.jpg" width="136" />
48 <figcaption>The Eagle in lunar orbit</figcaption>
49 </figure>
50 </div>
51
52 <blockquote>
53 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
54 </blockquote>
55
56 <figure class="caption" style="float:right"><img alt="The Eagle" src="../../image2/samples/assets/image2.jpg" width="200" />
57 <figcaption>The Eagle in lunar orbit</figcaption>
58 </figure>
59
60 <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
61
62 <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
63
64 <ol>
65 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
66 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
67 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
68 </ol>
69
70 <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
71
72 <figure class="caption"><img alt="Saturn V" height="129" src="../../image2/samples/assets/image1.jpg" width="101" />
73 <figcaption>Roll out of Saturn V on launch pad</figcaption>
74 </figure>
75
76 <hr />
77 <p style="text-align:right"><small>Source: <a href="http://en.wikipedia.org/wiki/Apollo_11">Wikipedia.org</a></small></p>
78
79 </textarea>
80
81 <h3>Inline Editor</h3>
82
83 <div id="editor2" contenteditable="true" style="outline: 2px solid #ccc">
84 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
85 <tbody>
86 <tr>
87 <td>This table</td>
88 <td>is the</td>
89 <td>very first</td>
90 <td>element of the document.</td>
91 </tr>
92 <tr>
93 <td>We are still</td>
94 <td>able to acces</td>
95 <td>the space before it.</td>
96 <td style="padding: 25px">
97 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
98 <tbody>
99 <tr>
100 <td>This table is inside of a cell of another table.</td>
101 </tr>
102 <tr>
103 <td>We can type&nbsp;either before or after it though.</td>
104 </tr>
105 </tbody>
106 </table>
107 </td>
108 </tr>
109 </tbody>
110 </table>
111
112 <hr />
113 <hr />
114 <ol style="width: 300px">
115 <li>This numbered list...</li>
116 <li>...is a neighbour of a horizontal line...</li>
117 <li style="padding: 20px;">
118 <ol>
119 <li>Nested list!</li>
120 </ol>
121 </li>
122 </ol>
123
124 <figure class="caption"><img alt="Saturn V" src="../../image2/samples/assets/image1.jpg" width="100" />
125 <figcaption>Roll out of Saturn V on launch pad</figcaption>
126 </figure>
127
128 <ul style="width: 450px">
129 <li>We can type between the lists...</li>
130 <li>...thanks to <strong>Magicline</strong>.</li>
131 </ul>
132
133 <p>Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.</p>
134
135 <p>Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.</p>
136
137 <p>Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.</p>
138
139 <div id="last" style="padding: 10px; text-align: center;">
140 <p>This text is wrapped in a&nbsp;<tt>DIV</tt>&nbsp;element. We can type after this element though.</p>
141 </div>
142 </div>
143
144 <script>
145
146 CKEDITOR.replace( 'editor1', {
147 extraPlugins: 'image2',
148 height: 450,
149 removePlugins: 'image,forms',
150 contentsCss: [ '../../../contents.css', '../../image2/samples/contents.css' ]
151 } );
152
153 CKEDITOR.inline( 'editor2', {
154 extraPlugins: 'image2',
155 height: 450,
156 removePlugins: 'image,forms'
157 } );
158
159 </script>
160
161 <div id="footer">
162 <hr>
163 <p>
164 CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
165 </p>
166 <p id="copy">
167 Copyright &copy; 2003-2017, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
168 Knabben. All rights reserved.
169 </p>
170 </div>
171</body>
172</html>
diff --git a/app/assets/javascripts/ckeditor/plugins/lineutils/dev/magicfinger.html b/app/assets/javascripts/ckeditor/plugins/lineutils/dev/magicfinger.html new file mode 100644 index 0000000..7f2b632 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/lineutils/dev/magicfinger.html
@@ -0,0 +1,285 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
4For licensing, see LICENSE.md or http://ckeditor.com/license
5-->
6<html>
7<head>
8 <meta charset="utf-8">
9 <title>Lineutils &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <link href="../../../samples/old/sample.css" rel="stylesheet">
12</head>
13<body>
14 <h1 class="samples">
15 <a href="../../../samples/old/index.html">CKEditor Samples</a> &raquo; Lineutils
16 </h1>
17
18 <h3>Classic (iframe-based) Editor</h3>
19
20 <textarea id="editor1" cols="10" rows="10">
21 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
22 <tbody>
23 <tr>
24 <td>This table</td>
25 <td>is the</td>
26 <td>very first</td>
27 <td>element of the document.</td>
28 </tr>
29 <tr>
30 <td>We are still</td>
31 <td>able to acces</td>
32 <td>the space before it.</td>
33 <td style="padding: 25px">
34 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
35 <tbody>
36 <tr>
37 <td>This table is inside of a cell of another table.</td>
38 </tr>
39 <tr>
40 <td>We can type&nbsp;either before or after it though.</td>
41 </tr>
42 </tbody>
43 </table>
44 </td>
45 </tr>
46 </tbody>
47 </table>
48
49 <p>Two succesive horizontal lines (<tt>HR</tt> tags). We can access the space in between:</p>
50
51 <hr />
52 <hr />
53 <ol style="width: 300px">
54 <li>This numbered list...</li>
55 <li>...is a neighbour of a horizontal line...</li>
56 <li style="padding: 20px;">
57 <ol>
58 <li>Nested list!</li>
59 </ol>
60 </li>
61 </ol>
62
63 <ul style="width: 450px">
64 <li>We can type between the lists...</li>
65 <li>...thanks to <strong>Magicline</strong>.</li>
66 </ul>
67
68 <p>Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.</p>
69
70 <p>Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.</p>
71
72 <p>Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.</p>
73
74 <div id="last" style="padding: 10px; text-align: center;">
75 <p>This text is wrapped in a&nbsp;<tt>DIV</tt>&nbsp;element. We can type after this element though.</p>
76 </div>
77 </textarea>
78
79 <h3>Inline Editor</h3>
80
81 <div id="editor2" contenteditable="true" style="outline: 2px solid #ccc">
82 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
83 <tbody>
84 <tr>
85 <td>This table</td>
86 <td>is the</td>
87 <td>very first</td>
88 <td>element of the document.</td>
89 </tr>
90 <tr>
91 <td>We are still</td>
92 <td>able to acces</td>
93 <td>the space before it.</td>
94 <td style="padding: 25px">
95 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
96 <tbody>
97 <tr>
98 <td>This table is inside of a cell of another table.</td>
99 </tr>
100 <tr>
101 <td>We can type&nbsp;either before or after it though.</td>
102 </tr>
103 </tbody>
104 </table>
105 </td>
106 </tr>
107 </tbody>
108 </table>
109
110 <p>Two succesive horizontal lines (<tt>HR</tt> tags). We can access the space in between:</p>
111
112 <hr />
113 <hr />
114 <ol style="width: 300px">
115 <li>This numbered list...</li>
116 <li>...is a neighbour of a horizontal line...</li>
117 <li style="padding: 20px;">
118 <ol>
119 <li>Nested list!</li>
120 </ol>
121 </li>
122 </ol>
123
124 <ul style="width: 450px">
125 <li>We can type between the lists...</li>
126 <li>...thanks to <strong>Magicline</strong>.</li>
127 </ul>
128
129 <p>Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.</p>
130
131 <p>Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.</p>
132
133 <p>Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.</p>
134
135 <div id="last" style="padding: 10px; text-align: center;">
136 <p>This text is wrapped in a&nbsp;<tt>DIV</tt>&nbsp;element. We can type after this element though.</p>
137 </div>
138 </div>
139
140 <h3>Extreme inline</h3>
141
142 <div id="editor3" contenteditable="true" style="left: 123px; outline: 1px solid red; border: 15px solid green; position: relative; top: 30; left: 30px;">
143 <div style="padding: 20px; background: gray; width: 300px" class="1">Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Curabitur et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet enim.</div>
144 <div style="background: violet; padding: 30px;" class="static">
145 Position static
146 <div style="background: green; padding: 30px; border: 14px solid orange">foo</div>
147 </div>
148 <dl class="2">
149 <dt>Key</dt><dd>Value</dd>
150 </dl>
151 <div>Whatever</div>
152 <hr id="hr">
153 <p>Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies</p>
154 <hr>
155 <hr>
156 <p>Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies</p>
157 <div style="background: green; padding: 30px; width: 200px">foo</div>
158 </div>
159
160 <h3>Classic (iframe-based) Editor, H-scroll</h3>
161
162 <textarea id="editor4" cols="10" rows="10">
163 <hr />
164 <hr />
165 <ol style="width: 1500px">
166 <li>This numbered list...</li>
167 <li>...is a neighbour of a horizontal line...</li>
168 <li style="padding: 20px;">
169 <ol>
170 <li>Nested list!</li>
171 </ol>
172 </li>
173 </ol>
174
175 <ul style="width: 450px">
176 <li>We can type between the lists...</li>
177 <li>...thanks to <strong>Magicline</strong>.</li>
178 </ul>
179
180 <p>Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.</p>
181
182 <p>Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.</p>
183
184 <p>Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.</p>
185
186 <div id="last" style="padding: 10px; text-align: center;">
187 <p>This text is wrapped in a&nbsp;<tt>DIV</tt>&nbsp;element. We can type after this element though.</p>
188 </div>
189 </textarea>
190
191 <script>
192
193 CKEDITOR.addCss(
194 '.cke_editable * { outline: 1px solid #BCEBFF }'
195 );
196
197 function callback() {
198 var helpers = CKEDITOR.plugins.lineutils;
199 var liner = new helpers.liner( this );
200 var locator = new helpers.locator( this );
201 var finder = new helpers.finder( this, {
202 lookups: {
203 'is block and first child': function( el ) {
204 if ( el.is( CKEDITOR.dtd.$listItem ) )
205 return;
206
207 if ( el.is( CKEDITOR.dtd.$block ) )
208 return CKEDITOR.LINEUTILS_BEFORE | CKEDITOR.LINEUTILS_AFTER;
209 }
210 }
211 } ).start( function( relations, x, y ) {
212 locator.locate( relations );
213
214 var locations = locator.locations,
215 uid, type;
216
217 liner.prepare( relations, locations );
218
219 for ( uid in locations ) {
220 for ( type in locations[ uid ] )
221 liner.placeLine( { uid: uid, type: type } );
222 }
223
224 liner.cleanup();
225 } );
226 }
227
228 CKEDITOR.disableAutoInline = true;
229
230 CKEDITOR.replace( 'editor1', {
231 extraPlugins: 'lineutils',
232 height: 450,
233 removePlugins: 'magicline',
234 allowedContent: true,
235 contentsCss: [ '../../../contents.css' ],
236 on: {
237 contentDom: callback
238 }
239 } );
240
241 CKEDITOR.inline( 'editor2', {
242 extraPlugins: 'lineutils',
243 removePlugins: 'magicline',
244 allowedContent: true,
245 contentsCss: [ '../../../contents.css' ],
246 on: {
247 contentDom: callback
248 }
249 } );
250
251 CKEDITOR.inline( 'editor3', {
252 extraPlugins: 'lineutils',
253 removePlugins: 'magicline',
254 allowedContent: true,
255 contentsCss: [ '../../../contents.css' ],
256 on: {
257 contentDom: callback
258 }
259 } );
260
261 CKEDITOR.replace( 'editor4', {
262 extraPlugins: 'lineutils',
263 removePlugins: 'magicline',
264 allowedContent: true,
265 contentsCss: [ '../../../contents.css' ],
266 on: {
267 contentDom: callback
268 }
269 } );
270
271
272 </script>
273
274 <div id="footer">
275 <hr>
276 <p>
277 CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
278 </p>
279 <p id="copy">
280 Copyright &copy; 2003-2017, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
281 Knabben. All rights reserved.
282 </p>
283 </div>
284</body>
285</html>
diff --git a/app/assets/javascripts/ckeditor/plugins/lineutils/plugin.js b/app/assets/javascripts/ckeditor/plugins/lineutils/plugin.js new file mode 100644 index 0000000..75d0da8 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/lineutils/plugin.js
@@ -0,0 +1,1018 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 /**
7 * @fileOverview A set of utilities to find and create horizontal spaces in edited content.
8 */
9
10'use strict';
11
12( function() {
13
14 CKEDITOR.plugins.add( 'lineutils' );
15
16 /**
17 * Determines a position relative to an element in DOM (before).
18 *
19 * @readonly
20 * @property {Number} [=0]
21 * @member CKEDITOR
22 */
23 CKEDITOR.LINEUTILS_BEFORE = 1;
24
25 /**
26 * Determines a position relative to an element in DOM (after).
27 *
28 * @readonly
29 * @property {Number} [=2]
30 * @member CKEDITOR
31 */
32 CKEDITOR.LINEUTILS_AFTER = 2;
33
34 /**
35 * Determines a position relative to an element in DOM (inside).
36 *
37 * @readonly
38 * @property {Number} [=4]
39 * @member CKEDITOR
40 */
41 CKEDITOR.LINEUTILS_INSIDE = 4;
42
43 /**
44 * A utility that traverses the DOM tree and discovers elements
45 * (relations) matching user-defined lookups.
46 *
47 * @private
48 * @class CKEDITOR.plugins.lineutils.finder
49 * @constructor Creates a Finder class instance.
50 * @param {CKEDITOR.editor} editor Editor instance that the Finder belongs to.
51 * @param {Object} def Finder's definition.
52 * @since 4.3
53 */
54 function Finder( editor, def ) {
55 CKEDITOR.tools.extend( this, {
56 editor: editor,
57 editable: editor.editable(),
58 doc: editor.document,
59 win: editor.window
60 }, def, true );
61
62 this.inline = this.editable.isInline();
63
64 if ( !this.inline ) {
65 this.frame = this.win.getFrame();
66 }
67
68 this.target = this[ this.inline ? 'editable' : 'doc' ];
69 }
70
71 Finder.prototype = {
72 /**
73 * Initializes searching for elements with every mousemove event fired.
74 * To stop searching use {@link #stop}.
75 *
76 * @param {Function} [callback] Function executed on every iteration.
77 */
78 start: function( callback ) {
79 var that = this,
80 editor = this.editor,
81 doc = this.doc,
82 el, elfp, x, y;
83
84 var moveBuffer = CKEDITOR.tools.eventsBuffer( 50, function() {
85 if ( editor.readOnly || editor.mode != 'wysiwyg' )
86 return;
87
88 that.relations = {};
89
90 // Sometimes it happens that elementFromPoint returns null (especially on IE).
91 // Any further traversal makes no sense if there's no start point. Abort.
92 // Note: In IE8 elementFromPoint may return zombie nodes of undefined nodeType,
93 // so rejecting those as well.
94 if ( !( elfp = doc.$.elementFromPoint( x, y ) ) || !elfp.nodeType ) {
95 return;
96 }
97
98 el = new CKEDITOR.dom.element( elfp );
99
100 that.traverseSearch( el );
101
102 if ( !isNaN( x + y ) ) {
103 that.pixelSearch( el, x, y );
104 }
105
106 callback && callback( that.relations, x, y );
107 } );
108
109 // Searching starting from element from point on mousemove.
110 this.listener = this.editable.attachListener( this.target, 'mousemove', function( evt ) {
111 x = evt.data.$.clientX;
112 y = evt.data.$.clientY;
113
114 moveBuffer.input();
115 } );
116
117 this.editable.attachListener( this.inline ? this.editable : this.frame, 'mouseout', function() {
118 moveBuffer.reset();
119 } );
120 },
121
122 /**
123 * Stops observing mouse events attached by {@link #start}.
124 */
125 stop: function() {
126 if ( this.listener ) {
127 this.listener.removeListener();
128 }
129 },
130
131 /**
132 * Returns a range representing the relation, according to its element
133 * and type.
134 *
135 * @param {Object} location Location containing a unique identifier and type.
136 * @returns {CKEDITOR.dom.range} Range representing the relation.
137 */
138 getRange: ( function() {
139 var where = {};
140
141 where[ CKEDITOR.LINEUTILS_BEFORE ] = CKEDITOR.POSITION_BEFORE_START;
142 where[ CKEDITOR.LINEUTILS_AFTER ] = CKEDITOR.POSITION_AFTER_END;
143 where[ CKEDITOR.LINEUTILS_INSIDE ] = CKEDITOR.POSITION_AFTER_START;
144
145 return function( location ) {
146 var range = this.editor.createRange();
147
148 range.moveToPosition( this.relations[ location.uid ].element, where[ location.type ] );
149
150 return range;
151 };
152 } )(),
153
154 /**
155 * Stores given relation in a {@link #relations} object. Processes the relation
156 * to normalize and avoid duplicates.
157 *
158 * @param {CKEDITOR.dom.element} el Element of the relation.
159 * @param {Number} type Relation, one of `CKEDITOR.LINEUTILS_AFTER`, `CKEDITOR.LINEUTILS_BEFORE`, `CKEDITOR.LINEUTILS_INSIDE`.
160 */
161 store: ( function() {
162 function merge( el, type, relations ) {
163 var uid = el.getUniqueId();
164
165 if ( uid in relations ) {
166 relations[ uid ].type |= type;
167 } else {
168 relations[ uid ] = { element: el, type: type };
169 }
170 }
171
172 return function( el, type ) {
173 var alt;
174
175 // Normalization to avoid duplicates:
176 // CKEDITOR.LINEUTILS_AFTER becomes CKEDITOR.LINEUTILS_BEFORE of el.getNext().
177 if ( is( type, CKEDITOR.LINEUTILS_AFTER ) && isStatic( alt = el.getNext() ) && alt.isVisible() ) {
178 merge( alt, CKEDITOR.LINEUTILS_BEFORE, this.relations );
179 type ^= CKEDITOR.LINEUTILS_AFTER;
180 }
181
182 // Normalization to avoid duplicates:
183 // CKEDITOR.LINEUTILS_INSIDE becomes CKEDITOR.LINEUTILS_BEFORE of el.getFirst().
184 if ( is( type, CKEDITOR.LINEUTILS_INSIDE ) && isStatic( alt = el.getFirst() ) && alt.isVisible() ) {
185 merge( alt, CKEDITOR.LINEUTILS_BEFORE, this.relations );
186 type ^= CKEDITOR.LINEUTILS_INSIDE;
187 }
188
189 merge( el, type, this.relations );
190 };
191 } )(),
192
193 /**
194 * Traverses the DOM tree towards root, checking all ancestors
195 * with lookup rules, avoiding duplicates. Stores positive relations
196 * in the {@link #relations} object.
197 *
198 * @param {CKEDITOR.dom.element} el Element which is the starting point.
199 */
200 traverseSearch: function( el ) {
201 var l, type, uid;
202
203 // Go down DOM towards root (or limit).
204 do {
205 uid = el.$[ 'data-cke-expando' ];
206
207 // This element was already visited and checked.
208 if ( uid && uid in this.relations ) {
209 continue;
210 }
211
212 if ( el.equals( this.editable ) ) {
213 return;
214 }
215
216 if ( isStatic( el ) ) {
217 // Collect all addresses yielded by lookups for that element.
218 for ( l in this.lookups ) {
219
220 if ( ( type = this.lookups[ l ]( el ) ) ) {
221 this.store( el, type );
222 }
223 }
224 }
225 } while ( !isLimit( el ) && ( el = el.getParent() ) );
226 },
227
228 /**
229 * Iterates vertically pixel-by-pixel within a given element starting
230 * from given coordinates, searching for elements in the neighborhood.
231 * Once an element is found it is processed by {@link #traverseSearch}.
232 *
233 * @param {CKEDITOR.dom.element} el Element which is the starting point.
234 * @param {Number} [x] Horizontal mouse coordinate relative to the viewport.
235 * @param {Number} [y] Vertical mouse coordinate relative to the viewport.
236 */
237 pixelSearch: ( function() {
238 var contains = CKEDITOR.env.ie || CKEDITOR.env.webkit ?
239 function( el, found ) {
240 return el.contains( found );
241 } : function( el, found ) {
242 return !!( el.compareDocumentPosition( found ) & 16 );
243 };
244
245 // Iterates pixel-by-pixel from starting coordinates, moving by defined
246 // step and getting elementFromPoint in every iteration. Iteration stops when:
247 // * A valid element is found.
248 // * Condition function returns `false` (i.e. reached boundaries of viewport).
249 // * No element is found (i.e. coordinates out of viewport).
250 // * Element found is ascendant of starting element.
251 //
252 // @param {Object} doc Native DOM document.
253 // @param {Object} el Native DOM element.
254 // @param {Number} xStart Horizontal starting coordinate to use.
255 // @param {Number} yStart Vertical starting coordinate to use.
256 // @param {Number} step Step of the algorithm.
257 // @param {Function} condition A condition relative to current vertical coordinate.
258 function iterate( el, xStart, yStart, step, condition ) {
259 var y = yStart,
260 tryouts = 0,
261 found;
262
263 while ( condition( y ) ) {
264 y += step;
265
266 // If we try and we try, and still nothing's found, let's end
267 // that party.
268 if ( ++tryouts == 25 ) {
269 return;
270 }
271
272 found = this.doc.$.elementFromPoint( xStart, y );
273
274 // Nothing found. This is crazy... but...
275 // It might be that a line, which is in different document,
276 // covers that pixel (elementFromPoint is doc-sensitive).
277 // Better let's have another try.
278 if ( !found ) {
279 continue;
280 }
281
282 // Still in the same element.
283 else if ( found == el ) {
284 tryouts = 0;
285 continue;
286 }
287
288 // Reached the edge of an element and found an ancestor or...
289 // A line, that covers that pixel. Better let's have another try.
290 else if ( !contains( el, found ) ) {
291 continue;
292 }
293
294 tryouts = 0;
295
296 // Found a valid element. Stop iterating.
297 if ( isStatic( ( found = new CKEDITOR.dom.element( found ) ) ) ) {
298 return found;
299 }
300 }
301 }
302
303 return function( el, x, y ) {
304 var paneHeight = this.win.getViewPaneSize().height,
305
306 // Try to find an element iterating *up* from the starting point.
307 neg = iterate.call( this, el.$, x, y, -1, function( y ) {
308 return y > 0;
309 } ),
310
311 // Try to find an element iterating *down* from the starting point.
312 pos = iterate.call( this, el.$, x, y, 1, function( y ) {
313 return y < paneHeight;
314 } );
315
316 if ( neg ) {
317 this.traverseSearch( neg );
318
319 // Iterate towards DOM root until neg is a direct child of el.
320 while ( !neg.getParent().equals( el ) ) {
321 neg = neg.getParent();
322 }
323 }
324
325 if ( pos ) {
326 this.traverseSearch( pos );
327
328 // Iterate towards DOM root until pos is a direct child of el.
329 while ( !pos.getParent().equals( el ) ) {
330 pos = pos.getParent();
331 }
332 }
333
334 // Iterate forwards starting from neg and backwards from
335 // pos to harvest all children of el between those elements.
336 // Stop when neg and pos meet each other or there's none of them.
337 // TODO (?) reduce number of hops forwards/backwards.
338 while ( neg || pos ) {
339 if ( neg ) {
340 neg = neg.getNext( isStatic );
341 }
342
343 if ( !neg || neg.equals( pos ) ) {
344 break;
345 }
346
347 this.traverseSearch( neg );
348
349 if ( pos ) {
350 pos = pos.getPrevious( isStatic );
351 }
352
353 if ( !pos || pos.equals( neg ) ) {
354 break;
355 }
356
357 this.traverseSearch( pos );
358 }
359 };
360 } )(),
361
362 /**
363 * Unlike {@link #traverseSearch}, it collects **all** elements from editable's DOM tree
364 * and runs lookups for every one of them, collecting relations.
365 *
366 * @returns {Object} {@link #relations}.
367 */
368 greedySearch: function() {
369 this.relations = {};
370
371 var all = this.editable.getElementsByTag( '*' ),
372 i = 0,
373 el, type, l;
374
375 while ( ( el = all.getItem( i++ ) ) ) {
376 // Don't consider editable, as it might be inline,
377 // and i.e. checking it's siblings is pointless.
378 if ( el.equals( this.editable ) ) {
379 continue;
380 }
381
382 // On IE8 element.getElementsByTagName returns comments... sic! (http://dev.ckeditor.com/ticket/13176)
383 if ( el.type != CKEDITOR.NODE_ELEMENT ) {
384 continue;
385 }
386
387 // Don't visit non-editable internals, for example widget's
388 // guts (above wrapper, below nested). Still check editable limits,
389 // as they are siblings with editable contents.
390 if ( !el.hasAttribute( 'contenteditable' ) && el.isReadOnly() ) {
391 continue;
392 }
393
394 if ( isStatic( el ) && el.isVisible() ) {
395 // Collect all addresses yielded by lookups for that element.
396 for ( l in this.lookups ) {
397 if ( ( type = this.lookups[ l ]( el ) ) ) {
398 this.store( el, type );
399 }
400 }
401 }
402 }
403
404 return this.relations;
405 }
406
407 /**
408 * Relations express elements in DOM that match user-defined {@link #lookups}.
409 * Every relation has its own `type` that determines whether
410 * it refers to the space before, after or inside the `element`.
411 * This object stores relations found by {@link #traverseSearch} or {@link #greedySearch}, structured
412 * in the following way:
413 *
414 * relations: {
415 * // Unique identifier of the element.
416 * Number: {
417 * // Element of this relation.
418 * element: {@link CKEDITOR.dom.element}
419 * // Conjunction of CKEDITOR.LINEUTILS_BEFORE, CKEDITOR.LINEUTILS_AFTER and CKEDITOR.LINEUTILS_INSIDE.
420 * type: Number
421 * },
422 * ...
423 * }
424 *
425 * @property {Object} relations
426 * @readonly
427 */
428
429 /**
430 * A set of user-defined functions used by Finder to check if an element
431 * is a valid relation, belonging to {@link #relations}.
432 * When the criterion is met, lookup returns a logical conjunction of `CKEDITOR.LINEUTILS_BEFORE`,
433 * `CKEDITOR.LINEUTILS_AFTER` or `CKEDITOR.LINEUTILS_INSIDE`.
434 *
435 * Lookups are passed along with Finder's definition.
436 *
437 * lookups: {
438 * 'some lookup': function( el ) {
439 * if ( someCondition )
440 * return CKEDITOR.LINEUTILS_BEFORE;
441 * },
442 * ...
443 * }
444 *
445 * @property {Object} lookups
446 */
447 };
448
449
450 /**
451 * A utility that analyses relations found by
452 * CKEDITOR.plugins.lineutils.finder and locates them
453 * in the viewport as horizontal lines of specific coordinates.
454 *
455 * @private
456 * @class CKEDITOR.plugins.lineutils.locator
457 * @constructor Creates a Locator class instance.
458 * @param {CKEDITOR.editor} editor Editor instance that Locator belongs to.
459 * @since 4.3
460 */
461 function Locator( editor, def ) {
462 CKEDITOR.tools.extend( this, def, {
463 editor: editor
464 }, true );
465 }
466
467 Locator.prototype = {
468 /**
469 * Locates the Y coordinate for all types of every single relation and stores
470 * them in an object.
471 *
472 * @param {Object} relations {@link CKEDITOR.plugins.lineutils.finder#relations}.
473 * @returns {Object} {@link #locations}.
474 */
475 locate: ( function() {
476 function locateSibling( rel, type ) {
477 var sib = rel.element[ type === CKEDITOR.LINEUTILS_BEFORE ? 'getPrevious' : 'getNext' ]();
478
479 // Return the middle point between siblings.
480 if ( sib && isStatic( sib ) ) {
481 rel.siblingRect = sib.getClientRect();
482
483 if ( type == CKEDITOR.LINEUTILS_BEFORE ) {
484 return ( rel.siblingRect.bottom + rel.elementRect.top ) / 2;
485 } else {
486 return ( rel.elementRect.bottom + rel.siblingRect.top ) / 2;
487 }
488 }
489
490 // If there's no sibling, use the edge of an element.
491 else {
492 if ( type == CKEDITOR.LINEUTILS_BEFORE ) {
493 return rel.elementRect.top;
494 } else {
495 return rel.elementRect.bottom;
496 }
497 }
498 }
499
500 return function( relations ) {
501 var rel;
502
503 this.locations = {};
504
505 for ( var uid in relations ) {
506 rel = relations[ uid ];
507 rel.elementRect = rel.element.getClientRect();
508
509 if ( is( rel.type, CKEDITOR.LINEUTILS_BEFORE ) ) {
510 this.store( uid, CKEDITOR.LINEUTILS_BEFORE, locateSibling( rel, CKEDITOR.LINEUTILS_BEFORE ) );
511 }
512
513 if ( is( rel.type, CKEDITOR.LINEUTILS_AFTER ) ) {
514 this.store( uid, CKEDITOR.LINEUTILS_AFTER, locateSibling( rel, CKEDITOR.LINEUTILS_AFTER ) );
515 }
516
517 // The middle point of the element.
518 if ( is( rel.type, CKEDITOR.LINEUTILS_INSIDE ) ) {
519 this.store( uid, CKEDITOR.LINEUTILS_INSIDE, ( rel.elementRect.top + rel.elementRect.bottom ) / 2 );
520 }
521 }
522
523 return this.locations;
524 };
525 } )(),
526
527 /**
528 * Calculates distances from every location to given vertical coordinate
529 * and sorts locations according to that distance.
530 *
531 * @param {Number} y The vertical coordinate used for sorting, used as a reference.
532 * @param {Number} [howMany] Determines the number of "closest locations" to be returned.
533 * @returns {Array} Sorted, array representation of {@link #locations}.
534 */
535 sort: ( function() {
536 var locations, sorted,
537 dist, i;
538
539 function distance( y, uid, type ) {
540 return Math.abs( y - locations[ uid ][ type ] );
541 }
542
543 return function( y, howMany ) {
544 locations = this.locations;
545 sorted = [];
546
547 for ( var uid in locations ) {
548 for ( var type in locations[ uid ] ) {
549 dist = distance( y, uid, type );
550
551 // An array is empty.
552 if ( !sorted.length ) {
553 sorted.push( { uid: +uid, type: type, dist: dist } );
554 } else {
555 // Sort the array on fly when it's populated.
556 for ( i = 0; i < sorted.length; i++ ) {
557 if ( dist < sorted[ i ].dist ) {
558 sorted.splice( i, 0, { uid: +uid, type: type, dist: dist } );
559 break;
560 }
561 }
562
563 // Nothing was inserted, so the distance is bigger than
564 // any of already calculated: push to the end.
565 if ( i == sorted.length ) {
566 sorted.push( { uid: +uid, type: type, dist: dist } );
567 }
568 }
569 }
570 }
571
572 if ( typeof howMany != 'undefined' ) {
573 return sorted.slice( 0, howMany );
574 } else {
575 return sorted;
576 }
577 };
578 } )(),
579
580 /**
581 * Stores the location in a collection.
582 *
583 * @param {Number} uid Unique identifier of the relation.
584 * @param {Number} type One of `CKEDITOR.LINEUTILS_BEFORE`, `CKEDITOR.LINEUTILS_AFTER` and `CKEDITOR.LINEUTILS_INSIDE`.
585 * @param {Number} y Vertical position of the relation.
586 */
587 store: function( uid, type, y ) {
588 if ( !this.locations[ uid ] ) {
589 this.locations[ uid ] = {};
590 }
591
592 this.locations[ uid ][ type ] = y;
593 }
594
595 /**
596 * @readonly
597 * @property {Object} locations
598 */
599 };
600
601 var tipCss = {
602 display: 'block',
603 width: '0px',
604 height: '0px',
605 'border-color': 'transparent',
606 'border-style': 'solid',
607 position: 'absolute',
608 top: '-6px'
609 },
610
611 lineStyle = {
612 height: '0px',
613 'border-top': '1px dashed red',
614 position: 'absolute',
615 'z-index': 9999
616 },
617
618 lineTpl =
619 '<div data-cke-lineutils-line="1" class="cke_reset_all" style="{lineStyle}">' +
620 '<span style="{tipLeftStyle}">&nbsp;</span>' +
621 '<span style="{tipRightStyle}">&nbsp;</span>' +
622 '</div>';
623
624 /**
625 * A utility that draws horizontal lines in DOM according to locations
626 * returned by CKEDITOR.plugins.lineutils.locator.
627 *
628 * @private
629 * @class CKEDITOR.plugins.lineutils.liner
630 * @constructor Creates a Liner class instance.
631 * @param {CKEDITOR.editor} editor Editor instance that Liner belongs to.
632 * @param {Object} def Liner's definition.
633 * @since 4.3
634 */
635 function Liner( editor, def ) {
636 var editable = editor.editable();
637
638 CKEDITOR.tools.extend( this, {
639 editor: editor,
640 editable: editable,
641 inline: editable.isInline(),
642 doc: editor.document,
643 win: editor.window,
644 container: CKEDITOR.document.getBody(),
645 winTop: CKEDITOR.document.getWindow()
646 }, def, true );
647
648 this.hidden = {};
649 this.visible = {};
650
651 if ( !this.inline ) {
652 this.frame = this.win.getFrame();
653 }
654
655 this.queryViewport();
656
657 // Callbacks must be wrapped. Otherwise they're not attached
658 // to global DOM objects (i.e. topmost window) for every editor
659 // because they're treated as duplicates. They belong to the
660 // same prototype shared among Liner instances.
661 var queryViewport = CKEDITOR.tools.bind( this.queryViewport, this ),
662 hideVisible = CKEDITOR.tools.bind( this.hideVisible, this ),
663 removeAll = CKEDITOR.tools.bind( this.removeAll, this );
664
665 editable.attachListener( this.winTop, 'resize', queryViewport );
666 editable.attachListener( this.winTop, 'scroll', queryViewport );
667
668 editable.attachListener( this.winTop, 'resize', hideVisible );
669 editable.attachListener( this.win, 'scroll', hideVisible );
670
671 editable.attachListener( this.inline ? editable : this.frame, 'mouseout', function( evt ) {
672 var x = evt.data.$.clientX,
673 y = evt.data.$.clientY;
674
675 this.queryViewport();
676
677 // Check if mouse is out of the element (iframe/editable).
678 if ( x <= this.rect.left || x >= this.rect.right || y <= this.rect.top || y >= this.rect.bottom ) {
679 this.hideVisible();
680 }
681
682 // Check if mouse is out of the top-window vieport.
683 if ( x <= 0 || x >= this.winTopPane.width || y <= 0 || y >= this.winTopPane.height ) {
684 this.hideVisible();
685 }
686 }, this );
687
688 editable.attachListener( editor, 'resize', queryViewport );
689 editable.attachListener( editor, 'mode', removeAll );
690 editor.on( 'destroy', removeAll );
691
692 this.lineTpl = new CKEDITOR.template( lineTpl ).output( {
693 lineStyle: CKEDITOR.tools.writeCssText(
694 CKEDITOR.tools.extend( {}, lineStyle, this.lineStyle, true )
695 ),
696 tipLeftStyle: CKEDITOR.tools.writeCssText(
697 CKEDITOR.tools.extend( {}, tipCss, {
698 left: '0px',
699 'border-left-color': 'red',
700 'border-width': '6px 0 6px 6px'
701 }, this.tipCss, this.tipLeftStyle, true )
702 ),
703 tipRightStyle: CKEDITOR.tools.writeCssText(
704 CKEDITOR.tools.extend( {}, tipCss, {
705 right: '0px',
706 'border-right-color': 'red',
707 'border-width': '6px 6px 6px 0'
708 }, this.tipCss, this.tipRightStyle, true )
709 )
710 } );
711 }
712
713 Liner.prototype = {
714 /**
715 * Permanently removes all lines (both hidden and visible) from DOM.
716 */
717 removeAll: function() {
718 var l;
719
720 for ( l in this.hidden ) {
721 this.hidden[ l ].remove();
722 delete this.hidden[ l ];
723 }
724
725 for ( l in this.visible ) {
726 this.visible[ l ].remove();
727 delete this.visible[ l ];
728 }
729 },
730
731 /**
732 * Hides a given line.
733 *
734 * @param {CKEDITOR.dom.element} line The line to be hidden.
735 */
736 hideLine: function( line ) {
737 var uid = line.getUniqueId();
738
739 line.hide();
740
741 this.hidden[ uid ] = line;
742 delete this.visible[ uid ];
743 },
744
745 /**
746 * Shows a given line.
747 *
748 * @param {CKEDITOR.dom.element} line The line to be shown.
749 */
750 showLine: function( line ) {
751 var uid = line.getUniqueId();
752
753 line.show();
754
755 this.visible[ uid ] = line;
756 delete this.hidden[ uid ];
757 },
758
759 /**
760 * Hides all visible lines.
761 */
762 hideVisible: function() {
763 for ( var l in this.visible ) {
764 this.hideLine( this.visible[ l ] );
765 }
766 },
767
768 /**
769 * Shows a line at given location.
770 *
771 * @param {Object} location Location object containing the unique identifier of the relation
772 * and its type. Usually returned by {@link CKEDITOR.plugins.lineutils.locator#sort}.
773 * @param {Function} [callback] A callback to be called once the line is shown.
774 */
775 placeLine: function( location, callback ) {
776 var styles, line, l;
777
778 // No style means that line would be out of viewport.
779 if ( !( styles = this.getStyle( location.uid, location.type ) ) ) {
780 return;
781 }
782
783 // Search for any visible line of a different hash first.
784 // It's faster to re-position visible line than to show it.
785 for ( l in this.visible ) {
786 if ( this.visible[ l ].getCustomData( 'hash' ) !== this.hash ) {
787 line = this.visible[ l ];
788 break;
789 }
790 }
791
792 // Search for any hidden line of a different hash.
793 if ( !line ) {
794 for ( l in this.hidden ) {
795 if ( this.hidden[ l ].getCustomData( 'hash' ) !== this.hash ) {
796 this.showLine( ( line = this.hidden[ l ] ) );
797 break;
798 }
799 }
800 }
801
802 // If no line available, add the new one.
803 if ( !line ) {
804 this.showLine( ( line = this.addLine() ) );
805 }
806
807 // Mark the line with current hash.
808 line.setCustomData( 'hash', this.hash );
809
810 // Mark the line as visible.
811 this.visible[ line.getUniqueId() ] = line;
812
813 line.setStyles( styles );
814
815 callback && callback( line );
816 },
817
818 /**
819 * Creates a style set to be used by the line, representing a particular
820 * relation (location).
821 *
822 * @param {Number} uid Unique identifier of the relation.
823 * @param {Number} type Type of the relation.
824 * @returns {Object} An object containing styles.
825 */
826 getStyle: function( uid, type ) {
827 var rel = this.relations[ uid ],
828 loc = this.locations[ uid ][ type ],
829 styles = {},
830 hdiff;
831
832 // Line should be between two elements.
833 if ( rel.siblingRect ) {
834 styles.width = Math.max( rel.siblingRect.width, rel.elementRect.width );
835 }
836 // Line is relative to a single element.
837 else {
838 styles.width = rel.elementRect.width;
839 }
840
841 // Let's calculate the vertical position of the line.
842 if ( this.inline ) {
843 // (http://dev.ckeditor.com/ticket/13155)
844 styles.top = loc + this.winTopScroll.y - this.rect.relativeY;
845 } else {
846 styles.top = this.rect.top + this.winTopScroll.y + loc;
847 }
848
849 // Check if line would be vertically out of the viewport.
850 if ( styles.top - this.winTopScroll.y < this.rect.top || styles.top - this.winTopScroll.y > this.rect.bottom ) {
851 return false;
852 }
853
854 // Now let's calculate the horizontal alignment (left and width).
855 if ( this.inline ) {
856 // (http://dev.ckeditor.com/ticket/13155)
857 styles.left = rel.elementRect.left - this.rect.relativeX;
858 } else {
859 if ( rel.elementRect.left > 0 )
860 styles.left = this.rect.left + rel.elementRect.left;
861
862 // H-scroll case. Left edge of element may be out of viewport.
863 else {
864 styles.width += rel.elementRect.left;
865 styles.left = this.rect.left;
866 }
867
868 // H-scroll case. Right edge of element may be out of viewport.
869 if ( ( hdiff = styles.left + styles.width - ( this.rect.left + this.winPane.width ) ) > 0 ) {
870 styles.width -= hdiff;
871 }
872 }
873
874 // Finally include horizontal scroll of the global window.
875 styles.left += this.winTopScroll.x;
876
877 // Append 'px' to style values.
878 for ( var style in styles ) {
879 styles[ style ] = CKEDITOR.tools.cssLength( styles[ style ] );
880 }
881
882 return styles;
883 },
884
885 /**
886 * Adds a new line to DOM.
887 *
888 * @returns {CKEDITOR.dom.element} A brand-new line.
889 */
890 addLine: function() {
891 var line = CKEDITOR.dom.element.createFromHtml( this.lineTpl );
892
893 line.appendTo( this.container );
894
895 return line;
896 },
897
898 /**
899 * Assigns a unique hash to the instance that is later used
900 * to tell unwanted lines from new ones. This method **must** be called
901 * before a new set of relations is to be visualized so {@link #cleanup}
902 * eventually hides obsolete lines. This is because lines
903 * are re-used between {@link #placeLine} calls and the number of
904 * necessary ones may vary depending on the number of relations.
905 *
906 * @param {Object} relations {@link CKEDITOR.plugins.lineutils.finder#relations}.
907 * @param {Object} locations {@link CKEDITOR.plugins.lineutils.locator#locations}.
908 */
909 prepare: function( relations, locations ) {
910 this.relations = relations;
911 this.locations = locations;
912 this.hash = Math.random();
913 },
914
915 /**
916 * Hides all visible lines that do not belong to current hash
917 * and no longer represent relations (locations).
918 *
919 * See also: {@link #prepare}.
920 */
921 cleanup: function() {
922 var line;
923
924 for ( var l in this.visible ) {
925 line = this.visible[ l ];
926
927 if ( line.getCustomData( 'hash' ) !== this.hash ) {
928 this.hideLine( line );
929 }
930 }
931 },
932
933 /**
934 * Queries dimensions of the viewport, editable, frame etc.
935 * that are used for correct positioning of the line.
936 */
937 queryViewport: function() {
938 this.winPane = this.win.getViewPaneSize();
939 this.winTopScroll = this.winTop.getScrollPosition();
940 this.winTopPane = this.winTop.getViewPaneSize();
941
942 // (http://dev.ckeditor.com/ticket/13155)
943 this.rect = this.getClientRect( this.inline ? this.editable : this.frame );
944 },
945
946 /**
947 * Returns `boundingClientRect` of an element, shifted by the position
948 * of `container` when the container is not `static` (http://dev.ckeditor.com/ticket/13155).
949 *
950 * See also: {@link CKEDITOR.dom.element#getClientRect}.
951 *
952 * @param {CKEDITOR.dom.element} el A DOM element.
953 * @returns {Object} A shifted rect, extended by `relativeY` and `relativeX` properties.
954 */
955 getClientRect: function( el ) {
956 var rect = el.getClientRect(),
957 relativeContainerDocPosition = this.container.getDocumentPosition(),
958 relativeContainerComputedPosition = this.container.getComputedStyle( 'position' );
959
960 // Static or not, those values are used to offset the position of the line so they cannot be undefined.
961 rect.relativeX = rect.relativeY = 0;
962
963 if ( relativeContainerComputedPosition != 'static' ) {
964 // Remember the offset used to shift the clientRect.
965 rect.relativeY = relativeContainerDocPosition.y;
966 rect.relativeX = relativeContainerDocPosition.x;
967
968 rect.top -= rect.relativeY;
969 rect.bottom -= rect.relativeY;
970 rect.left -= rect.relativeX;
971 rect.right -= rect.relativeX;
972 }
973
974 return rect;
975 }
976 };
977
978 function is( type, flag ) {
979 return type & flag;
980 }
981
982 var floats = { left: 1, right: 1, center: 1 },
983 positions = { absolute: 1, fixed: 1 };
984
985 function isElement( node ) {
986 return node && node.type == CKEDITOR.NODE_ELEMENT;
987 }
988
989 function isFloated( el ) {
990 return !!( floats[ el.getComputedStyle( 'float' ) ] || floats[ el.getAttribute( 'align' ) ] );
991 }
992
993 function isPositioned( el ) {
994 return !!positions[ el.getComputedStyle( 'position' ) ];
995 }
996
997 function isLimit( node ) {
998 return isElement( node ) && node.getAttribute( 'contenteditable' ) == 'true';
999 }
1000
1001 function isStatic( node ) {
1002 return isElement( node ) && !isFloated( node ) && !isPositioned( node );
1003 }
1004
1005 /**
1006 * Global namespace storing definitions and global helpers for the Line Utilities plugin.
1007 *
1008 * @private
1009 * @class
1010 * @singleton
1011 * @since 4.3
1012 */
1013 CKEDITOR.plugins.lineutils = {
1014 finder: Finder,
1015 locator: Locator,
1016 liner: Liner
1017 };
1018} )();
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/contents.css b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/contents.css new file mode 100644 index 0000000..c2b51d3 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/contents.css
@@ -0,0 +1,23 @@
1.mediumBorder {
2 border-width: 2px;
3}
4.thickBorder {
5 border-width: 5px;
6}
7img.thickBorder, img.mediumBorder {
8 border-style: solid;
9 border-color: #CCC;
10}
11.important.soMuch {
12 margin: 25px;
13 padding: 25px;
14 background: red;
15 border: none;
16}
17
18span.redMarker {
19 background-color: red;
20}
21.invisible {
22 opacity: 0.1;
23} \ No newline at end of file
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/sample.jpg b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/sample.jpg new file mode 100644 index 0000000..a4a77fa --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/sample.jpg
Binary files differ
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/contents.css b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/contents.css new file mode 100644 index 0000000..dba3015 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/contents.css
@@ -0,0 +1,36 @@
1.simplebox {
2 padding: 8px;
3 margin: 10px;
4 background: #eee;
5 border-radius: 8px;
6 border: 1px solid #ddd;
7 box-shadow: 0 1px 1px #fff inset, 0 -1px 0px #ccc inset;
8}
9.simplebox-title, .simplebox-content {
10 box-shadow: 0 1px 1px #ddd inset;
11 border: 1px solid #cccccc;
12 border-radius: 5px;
13 background: #fff;
14}
15.simplebox-title {
16 margin: 0 0 8px;
17 padding: 5px 8px;
18}
19.simplebox-content {
20 padding: 0 8px;
21}
22.simplebox-content::after {
23 content: '';
24 display: block;
25 clear: both;
26}
27.simplebox.align-right {
28 float: right;
29}
30.simplebox.align-left {
31 float: left;
32}
33.simplebox.align-center {
34 margin-left: auto;
35 margin-right: auto;
36} \ No newline at end of file
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js new file mode 100644 index 0000000..f0cdb4d --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js
@@ -0,0 +1,51 @@
1// Note: This automatic widget to dialog window binding (the fact that every field is set up from the widget
2// and is committed to the widget) is only possible when the dialog is opened by the Widgets System
3// (i.e. the widgetDef.dialog property is set).
4// When you are opening the dialog window by yourself, you need to take care of this by yourself too.
5
6CKEDITOR.dialog.add( 'simplebox', function( editor ) {
7 return {
8 title: 'Edit Simple Box',
9 minWidth: 200,
10 minHeight: 100,
11 contents: [
12 {
13 id: 'info',
14 elements: [
15 {
16 id: 'align',
17 type: 'select',
18 label: 'Align',
19 items: [
20 [ editor.lang.common.notSet, '' ],
21 [ editor.lang.common.alignLeft, 'left' ],
22 [ editor.lang.common.alignRight, 'right' ],
23 [ editor.lang.common.alignCenter, 'center' ]
24 ],
25 // When setting up this field, set its value to the "align" value from widget data.
26 // Note: Align values used in the widget need to be the same as those defined in the "items" array above.
27 setup: function( widget ) {
28 this.setValue( widget.data.align );
29 },
30 // When committing (saving) this field, set its value to the widget data.
31 commit: function( widget ) {
32 widget.setData( 'align', this.getValue() );
33 }
34 },
35 {
36 id: 'width',
37 type: 'text',
38 label: 'Width',
39 width: '50px',
40 setup: function( widget ) {
41 this.setValue( widget.data.width );
42 },
43 commit: function( widget ) {
44 widget.setData( 'width', this.getValue() );
45 }
46 }
47 ]
48 }
49 ]
50 };
51} ); \ No newline at end of file
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/icons/simplebox.png b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/icons/simplebox.png new file mode 100644 index 0000000..6a5e313 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/icons/simplebox.png
Binary files differ
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/plugin.js b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/plugin.js new file mode 100644 index 0000000..3e7c99c --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/plugin.js
@@ -0,0 +1,114 @@
1'use strict';
2
3// Register the plugin within the editor.
4CKEDITOR.plugins.add( 'simplebox', {
5 // This plugin requires the Widgets System defined in the 'widget' plugin.
6 requires: 'widget,dialog',
7
8 // Register the icon used for the toolbar button. It must be the same
9 // as the name of the widget.
10 icons: 'simplebox',
11
12 // The plugin initialization logic goes inside this method.
13 init: function( editor ) {
14 // Register the editing dialog.
15 CKEDITOR.dialog.add( 'simplebox', this.path + 'dialogs/simplebox.js' );
16
17 // Register the simplebox widget.
18 editor.widgets.add( 'simplebox', {
19 // Allow all HTML elements, classes, and styles that this widget requires.
20 // Read more about the Advanced Content Filter here:
21 // * http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter
22 // * http://docs.ckeditor.com/#!/guide/plugin_sdk_integration_with_acf
23 allowedContent:
24 'div(!simplebox,align-left,align-right,align-center){width};' +
25 'div(!simplebox-content); h2(!simplebox-title)',
26
27 // Minimum HTML which is required by this widget to work.
28 requiredContent: 'div(simplebox)',
29
30 // Define two nested editable areas.
31 editables: {
32 title: {
33 // Define CSS selector used for finding the element inside widget element.
34 selector: '.simplebox-title',
35 // Define content allowed in this nested editable. Its content will be
36 // filtered accordingly and the toolbar will be adjusted when this editable
37 // is focused.
38 allowedContent: 'br strong em'
39 },
40 content: {
41 selector: '.simplebox-content'
42 }
43 },
44
45 // Define the template of a new Simple Box widget.
46 // The template will be used when creating new instances of the Simple Box widget.
47 template:
48 '<div class="simplebox">' +
49 '<h2 class="simplebox-title">Title</h2>' +
50 '<div class="simplebox-content"><p>Content...</p></div>' +
51 '</div>',
52
53 // Define the label for a widget toolbar button which will be automatically
54 // created by the Widgets System. This button will insert a new widget instance
55 // created from the template defined above, or will edit selected widget
56 // (see second part of this tutorial to learn about editing widgets).
57 //
58 // Note: In order to be able to translate your widget you should use the
59 // editor.lang.simplebox.* property. A string was used directly here to simplify this tutorial.
60 button: 'Create a simple box',
61
62 // Set the widget dialog window name. This enables the automatic widget-dialog binding.
63 // This dialog window will be opened when creating a new widget or editing an existing one.
64 dialog: 'simplebox',
65
66 // Check the elements that need to be converted to widgets.
67 //
68 // Note: The "element" argument is an instance of http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.element
69 // so it is not a real DOM element yet. This is caused by the fact that upcasting is performed
70 // during data processing which is done on DOM represented by JavaScript objects.
71 upcast: function( element ) {
72 // Return "true" (that element needs to converted to a Simple Box widget)
73 // for all <div> elements with a "simplebox" class.
74 return element.name == 'div' && element.hasClass( 'simplebox' );
75 },
76
77 // When a widget is being initialized, we need to read the data ("align" and "width")
78 // from DOM and set it by using the widget.setData() method.
79 // More code which needs to be executed when DOM is available may go here.
80 init: function() {
81 var width = this.element.getStyle( 'width' );
82 if ( width )
83 this.setData( 'width', width );
84
85 if ( this.element.hasClass( 'align-left' ) )
86 this.setData( 'align', 'left' );
87 if ( this.element.hasClass( 'align-right' ) )
88 this.setData( 'align', 'right' );
89 if ( this.element.hasClass( 'align-center' ) )
90 this.setData( 'align', 'center' );
91 },
92
93 // Listen on the widget#data event which is fired every time the widget data changes
94 // and updates the widget's view.
95 // Data may be changed by using the widget.setData() method, which we use in the
96 // Simple Box dialog window.
97 data: function() {
98 // Check whether "width" widget data is set and remove or set "width" CSS style.
99 // The style is set on widget main element (div.simplebox).
100 if ( !this.data.width )
101 this.element.removeStyle( 'width' );
102 else
103 this.element.setStyle( 'width', this.data.width );
104
105 // Brutally remove all align classes and set a new one if "align" widget data is set.
106 this.element.removeClass( 'align-left' );
107 this.element.removeClass( 'align-right' );
108 this.element.removeClass( 'align-center' );
109 if ( this.data.align )
110 this.element.addClass( 'align-' + this.data.align );
111 }
112 } );
113 }
114} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/dev/console.js b/app/assets/javascripts/ckeditor/plugins/widget/dev/console.js new file mode 100644 index 0000000..1404a2c --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/dev/console.js
@@ -0,0 +1,131 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6/* global CKCONSOLE */
7
8'use strict';
9
10( function() {
11
12 CKCONSOLE.add( 'widget', {
13 panels: [
14 {
15 type: 'box',
16 content: '<ul class="ckconsole_list ckconsole_value" data-value="instances"></ul>',
17
18 refresh: function( editor ) {
19 var instances = obj2Array( editor.widgets.instances );
20
21 return {
22 header: 'Instances (' + instances.length + ')',
23 instances: generateInstancesList( instances )
24 };
25 },
26
27 refreshOn: function( editor, refresh ) {
28 editor.widgets.on( 'instanceCreated', function( evt ) {
29 refresh();
30
31 evt.data.on( 'data', refresh );
32 } );
33
34 editor.widgets.on( 'instanceDestroyed', refresh );
35 }
36 },
37
38 {
39 type: 'box',
40 content:
41 '<ul class="ckconsole_list">' +
42 '<li>focused: <span class="ckconsole_value" data-value="focused"></span></li>' +
43 '<li>selected: <span class="ckconsole_value" data-value="selected"></span></li>' +
44 '</ul>',
45
46 refresh: function( editor ) {
47 var focused = editor.widgets.focused,
48 selected = editor.widgets.selected,
49 selectedIds = [];
50
51 for ( var i = 0; i < selected.length; ++i )
52 selectedIds.push( selected[ i ].id );
53
54 return {
55 header: 'Focus &amp; selection',
56 focused: focused ? 'id: ' + focused.id : '-',
57 selected: selectedIds.length ? 'id: ' + selectedIds.join( ', id: ' ) : '-'
58 };
59 },
60
61 refreshOn: function( editor, refresh ) {
62 editor.on( 'selectionCheck', refresh, null, null, 999 );
63 }
64 },
65
66 {
67 type: 'log',
68
69 on: function( editor, log, logFn ) {
70 // Add all listeners with high priorities to log
71 // messages in the correct order when one event depends on another.
72 // E.g. selectionChange triggers widget selection - if this listener
73 // for selectionChange will be executed later than that one, then order
74 // will be incorrect.
75
76 editor.on( 'selectionChange', function( evt ) {
77 var msg = 'selection change',
78 sel = evt.data.selection,
79 el = sel.getSelectedElement(),
80 widget;
81
82 if ( el && ( widget = editor.widgets.getByElement( el, true ) ) )
83 msg += ' (id: ' + widget.id + ')';
84
85 log( msg );
86 }, null, null, 1 );
87
88 editor.widgets.on( 'instanceDestroyed', function( evt ) {
89 log( 'instance destroyed (id: ' + evt.data.id + ')' );
90 }, null, null, 1 );
91
92 editor.widgets.on( 'instanceCreated', function( evt ) {
93 log( 'instance created (id: ' + evt.data.id + ')' );
94 }, null, null, 1 );
95
96 editor.widgets.on( 'widgetFocused', function( evt ) {
97 log( 'widget focused (id: ' + evt.data.widget.id + ')' );
98 }, null, null, 1 );
99
100 editor.widgets.on( 'widgetBlurred', function( evt ) {
101 log( 'widget blurred (id: ' + evt.data.widget.id + ')' );
102 }, null, null, 1 );
103
104 editor.widgets.on( 'checkWidgets', logFn( 'checking widgets' ), null, null, 1 );
105 editor.widgets.on( 'checkSelection', logFn( 'checking selection' ), null, null, 1 );
106 }
107 }
108 ]
109 } );
110
111 function generateInstancesList( instances ) {
112 var html = '',
113 instance;
114
115 for ( var i = 0; i < instances.length; ++i ) {
116 instance = instances[ i ];
117 html += itemTpl.output( { id: instance.id, name: instance.name, data: JSON.stringify( instance.data ) } );
118 }
119 return html;
120 }
121
122 function obj2Array( obj ) {
123 var arr = [];
124 for ( var id in obj )
125 arr.push( obj[ id ] );
126
127 return arr;
128 }
129
130 var itemTpl = new CKEDITOR.template( '<li>id: <code>{id}</code>, name: <code>{name}</code>, data: <code>{data}</code></li>' );
131} )();
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/dev/nestedwidgets.html b/app/assets/javascripts/ckeditor/plugins/widget/dev/nestedwidgets.html new file mode 100644 index 0000000..0bc998b --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/dev/nestedwidgets.html
@@ -0,0 +1,134 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
4For licensing, see LICENSE.md or http://ckeditor.com/license
5-->
6<html>
7<head>
8 <meta charset="utf-8">
9 <title>Nested widgets &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <script src="../../../dev/console/console.js"></script>
12 <script src="../../../dev/console/focusconsole.js"></script>
13 <script src="console.js"></script>
14 <link rel="stylesheet" href="../../../samples/old/sample.css">
15 <link rel="stylesheet" href="../../../contents.css">
16 <link rel="stylesheet" href="assets/simplebox/contents.css">
17</head>
18<body>
19 <h1 class="samples">Nested widgets</h1>
20
21 <h2>Classic (iframe-based) Sample</h2>
22 <textarea cols="80" id="editor1" name="editor1" rows="10">
23 <h1>Simple Box Sample</h1>
24
25 <div class="simplebox align-right" style="width: 60%">
26 <h2 class="simplebox-title">Title</h2>
27 <div class="simplebox-content">
28 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
29
30 <figure class="image" style="float: right">
31 <img alt="The Eagle" src="assets/sample.jpg" width="150" />
32 <figcaption>The Eagle in lunar orbit</figcaption>
33 </figure>
34
35 <ul>
36 <li>Foo!</li>
37 <li>Bar!</li>
38 </ul>
39
40 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
41 </div>
42 </div>
43
44 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
45
46 <p>Pellentesque vitae eleifend nisl, non accumsan tellus. Maecenas nec libero non tellus tincidunt mollis porttitor sed arcu. Donec ultricies nulla vitae eros lacinia, vel congue sem auctor. Vivamus convallis, urna ac tincidunt malesuada, lectus erat convallis metus, a hendrerit massa augue accumsan magna. Nulla mattis tellus elit, nec congue magna scelerisque eget. Aliquam posuere nisi augue, posuere sodales nisi iaculis eu. Donec fermentum urna id nibh sagittis fermentum sit amet sed enim. Aliquam neque elit, pretium elementum nunc a, faucibus accumsan lorem. Etiam pulvinar odio et hendrerit tincidunt. Suspendisse tempus eros lacus, in convallis velit mollis ut. Aenean congue, justo eleifend ultricies malesuada, nunc nunc molestie mauris, eget placerat libero eros vel nisi. Quisque diam arcu, mollis ac laoreet vitae, varius et sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis in vehicula sapien. Nunc feugiat porta elit nec volutpat.</p>
47
48 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
49
50 <div class="simplebox align-center" style="width: 750px">
51 <h2 class="simplebox-title">Title</h2>
52 <div class="simplebox-content">
53 <p><img alt="The Eagle" src="assets/sample.jpg" width="150" style="float: left" /><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
54
55 <ul>
56 <li>Foo!</li>
57 <li>Bar!</li>
58 </ul>
59 </div>
60 </div>
61
62 <p>Ut eget ipsum a sapien porta ultrices. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus mi lacus, pharetra eu bibendum blandit, tristique sit amet leo. Integer eu nulla nec magna vulputate blandit. Praesent mattis quis ante eget adipiscing. Nulla vel tempus risus, in placerat velit. Mauris sed nibh at elit posuere laoreet. Morbi non sapien sed nunc fringilla imperdiet.</p>
63 </textarea>
64
65 <h2>Inline Sample</h2>
66 <div id="editor2" contenteditable="true">
67 <h1>Simple Box Sample</h1>
68
69 <div class="simplebox align-right" style="width: 60%">
70 <h2 class="simplebox-title">Title</h2>
71 <div class="simplebox-content">
72 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
73
74 <figure class="image" style="float: right">
75 <img alt="The Eagle" src="assets/sample.jpg" width="150" />
76 <figcaption>The Eagle in lunar orbit</figcaption>
77 </figure>
78
79 <ul>
80 <li>Foo!</li>
81 <li>Bar!</li>
82 </ul>
83
84 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
85 </div>
86 </div>
87
88 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
89
90 <p>Pellentesque vitae eleifend nisl, non accumsan tellus. Maecenas nec libero non tellus tincidunt mollis porttitor sed arcu. Donec ultricies nulla vitae eros lacinia, vel congue sem auctor. Vivamus convallis, urna ac tincidunt malesuada, lectus erat convallis metus, a hendrerit massa augue accumsan magna. Nulla mattis tellus elit, nec congue magna scelerisque eget. Aliquam posuere nisi augue, posuere sodales nisi iaculis eu. Donec fermentum urna id nibh sagittis fermentum sit amet sed enim. Aliquam neque elit, pretium elementum nunc a, faucibus accumsan lorem. Etiam pulvinar odio et hendrerit tincidunt. Suspendisse tempus eros lacus, in convallis velit mollis ut. Aenean congue, justo eleifend ultricies malesuada, nunc nunc molestie mauris, eget placerat libero eros vel nisi. Quisque diam arcu, mollis ac laoreet vitae, varius et sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis in vehicula sapien. Nunc feugiat porta elit nec volutpat.</p>
91
92 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
93
94 <div class="simplebox align-center" style="width: 750px">
95 <h2 class="simplebox-title">Title</h2>
96 <div class="simplebox-content">
97 <p><img alt="The Eagle" src="assets/sample.jpg" width="150" style="float: left" /><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
98
99 <ul>
100 <li>Foo!</li>
101 <li>Bar!</li>
102 </ul>
103 </div>
104 </div>
105
106 <p>Ut eget ipsum a sapien porta ultrices. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus mi lacus, pharetra eu bibendum blandit, tristique sit amet leo. Integer eu nulla nec magna vulputate blandit. Praesent mattis quis ante eget adipiscing. Nulla vel tempus risus, in placerat velit. Mauris sed nibh at elit posuere laoreet. Morbi non sapien sed nunc fringilla imperdiet.</p>
107 </div>
108
109 <script>
110 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
111 CKEDITOR.tools.enableHtml5Elements( document );
112
113 CKEDITOR.plugins.addExternal( 'simplebox', 'plugins/widget/dev/assets/simplebox/' );
114
115 CKEDITOR.replace( 'editor1', {
116 extraPlugins: 'simplebox,placeholder,image2',
117 removePlugins: 'forms,bidi',
118 contentsCss: [ '../../../contents.css', 'assets/simplebox/contents.css' ],
119 height: 500
120 } );
121
122 CKEDITOR.inline( 'editor2', {
123 extraPlugins: 'simplebox,placeholder,image2',
124 removePlugins: 'forms,bidi'
125 } );
126
127 CKCONSOLE.create( 'widget', { editor: 'editor1' } );
128 CKCONSOLE.create( 'focus', { editor: 'editor1' } );
129 CKCONSOLE.create( 'widget', { editor: 'editor2', folded: true } );
130 CKCONSOLE.create( 'focus', { editor: 'editor2', folded: true } );
131
132 </script>
133</body>
134</html>
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/dev/widgetstyles.html b/app/assets/javascripts/ckeditor/plugins/widget/dev/widgetstyles.html new file mode 100644 index 0000000..da58e44 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/dev/widgetstyles.html
@@ -0,0 +1,144 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
4For licensing, see LICENSE.md or http://ckeditor.com/license
5-->
6<html>
7<head>
8 <meta charset="utf-8">
9 <title>Applying styles to widgets &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <link rel="stylesheet" href="../../../samples/old/sample.css">
12 <link rel="stylesheet" href="../../../contents.css">
13 <link rel="stylesheet" href="assets/contents.css">
14</head>
15<body>
16 <h1 class="samples">Applying styles to widgets</h1>
17
18 <h2>Classic (iframe-based) Sample</h2>
19 <textarea cols="80" id="editor1" name="editor1" rows="10">
20 <h1>Apollo 11</h1>
21
22 <figure class="image" style="float: right">
23 <img alt="Saturn V" src="../../../samples/assets/sample.jpg" width="150" />
24 <figcaption>Roll out of Saturn V on launch pad</figcaption>
25 </figure>
26
27 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
28
29 <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
30
31 <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
32
33 <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
34
35 <blockquote>
36 <p>One small step for [a] man, one giant leap for mankind.</p>
37 </blockquote>
38
39 <p><span class="math-tex">\( \left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right) \)</span></p>
40
41 <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p>
42
43 <blockquote>
44 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
45 </blockquote>
46
47 <figure class="image" style="float: right">
48 <img alt="The Eagle" src="../../../samples/assets/sample.jpg" width="150" />
49 <figcaption>The Eagle in lunar orbit</figcaption>
50 </figure>
51
52 <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
53
54 <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
55
56 <ol>
57 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
58 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
59 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
60 </ol>
61
62 <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
63 </textarea>
64
65 <h2>Inline Sample</h2>
66 <div id="editor2" contenteditable="true">
67 <h1>Apollo 11</h1>
68
69 <figure class="image" style="float: right">
70 <img alt="Saturn V" src="../../../samples/assets/sample.jpg" width="150" />
71 <figcaption>Roll out of Saturn V on launch pad</figcaption>
72 </figure>
73
74 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
75
76 <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
77
78 <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
79
80 <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
81
82 <blockquote>
83 <p>One small step for [a] man, one giant leap for mankind.</p>
84 </blockquote>
85
86 <p><span class="math-tex">\( \left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right) \)</span></p>
87
88 <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p>
89
90 <blockquote>
91 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
92 </blockquote>
93
94 <figure class="image" style="float: right">
95 <img alt="The Eagle" src="../../../samples/assets/sample.jpg" width="150" />
96 <figcaption>The Eagle in lunar orbit</figcaption>
97 </figure>
98
99 <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
100
101 <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
102
103 <ol>
104 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
105 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
106 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
107 </ol>
108
109 <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
110 </div>
111
112 <script>
113 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
114 CKEDITOR.tools.enableHtml5Elements( document );
115
116 CKEDITOR.disableAutoInline = true;
117
118 var stylesSet = [
119 { name: 'Medium border', type: 'widget', widget: 'image', attributes: { 'class': 'mediumBorder' } },
120 { name: 'Thick border', type: 'widget', widget: 'image', attributes: { 'class': 'thickBorder' } },
121 { name: 'So important', type: 'widget', widget: 'image', attributes: { 'class': 'important soMuch' } },
122
123 { name: 'Red marker', type: 'widget', widget: 'placeholder', attributes: { 'class': 'redMarker' } },
124 { name: 'Invisible Placeholder', type: 'widget', widget: 'placeholder', attributes: { 'class': 'invisible' } },
125
126 { name: 'Invisible Mathjax', type: 'widget', widget: 'mathjax', attributes: { 'class': 'invisible' } }
127 ];
128
129 CKEDITOR.replace( 'editor1', {
130 extraPlugins: 'placeholder,image2,mathjax',
131 contentsCss: [ '../../../contents.css', 'assets/contents.css' ],
132 stylesSet: stylesSet,
133 height: 300
134 } );
135
136 CKEDITOR.inline( 'editor2', {
137 extraPlugins: 'placeholder,image2,mathjax',
138 stylesSet: stylesSet,
139 height: 300
140 } );
141
142 </script>
143</body>
144</html>
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/images/handle.png b/app/assets/javascripts/ckeditor/plugins/widget/images/handle.png new file mode 100644 index 0000000..ba8cda5 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/images/handle.png
Binary files differ
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/af.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/af.js new file mode 100644 index 0000000..c05e1b1 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/af.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'af', {
6 'move': 'Klik en trek on te beweeg',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/ar.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/ar.js new file mode 100644 index 0000000..f53676a --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/ar.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ar', {
6 'move': 'إضغط و إسحب للتحريك',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/az.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/az.js new file mode 100644 index 0000000..ed7d2e5 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/az.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'az', {
6 'move': 'Tıklayın və aparın',
7 'label': '%1 vidjet'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/bg.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/bg.js new file mode 100644 index 0000000..8b1ad01 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/bg.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'bg', {
6 'move': 'Кликни и влачи, за да преместиш',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/ca.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/ca.js new file mode 100644 index 0000000..7572f0e --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/ca.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ca', {
6 'move': 'Clicar i arrossegar per moure',
7 'label': '%1 widget'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/cs.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/cs.js new file mode 100644 index 0000000..ba16149 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/cs.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'cs', {
6 'move': 'Klepněte a táhněte pro přesunutí',
7 'label': 'Ovládací prvek %1'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/cy.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/cy.js new file mode 100644 index 0000000..9bbc89a --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/cy.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'cy', {
6 'move': 'Clcio a llusgo i symud',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/da.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/da.js new file mode 100644 index 0000000..a6a1bb7 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/da.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'da', {
6 'move': 'Klik og træk for at flytte',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/de-ch.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/de-ch.js new file mode 100644 index 0000000..d409bb6 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/de-ch.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'de-ch', {
6 'move': 'Zum Verschieben anwählen und ziehen',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/de.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/de.js new file mode 100644 index 0000000..eb05fb8 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/de.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'de', {
6 'move': 'Zum Verschieben anwählen und ziehen',
7 'label': '%1 Steuerelement'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/el.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/el.js new file mode 100644 index 0000000..4ea9504 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/el.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'el', {
6 'move': 'Κάνετε κλικ και σύρετε το ποντίκι για να μετακινήστε',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/en-gb.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/en-gb.js new file mode 100644 index 0000000..ad1a209 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/en-gb.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'en-gb', {
6 'move': 'Click and drag to move',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/en.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/en.js new file mode 100644 index 0000000..d2a43cb --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/en.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'en', {
6 'move': 'Click and drag to move',
7 'label': '%1 widget'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/eo.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/eo.js new file mode 100644 index 0000000..5801514 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/eo.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'eo', {
6 'move': 'klaki kaj treni por movi',
7 'label': '%1 fenestraĵo'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/es-mx.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/es-mx.js new file mode 100644 index 0000000..028901d --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/es-mx.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'es-mx', {
6 'move': 'Presiona y arrastra para mover',
7 'label': '%1 widget'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/es.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/es.js new file mode 100644 index 0000000..89d1639 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/es.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'es', {
6 'move': 'Dar clic y arrastrar para mover',
7 'label': 'reproductor %1'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/eu.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/eu.js new file mode 100644 index 0000000..5a3d126 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/eu.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'eu', {
6 'move': 'Klikatu eta arrastatu lekuz aldatzeko',
7 'label': '%1 widget'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/fa.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/fa.js new file mode 100644 index 0000000..16c27bf --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/fa.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'fa', {
6 'move': 'کلیک و کشیدن برای جابجایی',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/fi.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/fi.js new file mode 100644 index 0000000..a4d6e2f --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/fi.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'fi', {
6 'move': 'Siirrä klikkaamalla ja raahaamalla',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/fr.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/fr.js new file mode 100644 index 0000000..f7f87e8 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/fr.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'fr', {
6 'move': 'Cliquer et glisser pour déplacer',
7 'label': 'Élément %1'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/gl.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/gl.js new file mode 100644 index 0000000..a480371 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/gl.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'gl', {
6 'move': 'Prema e arrastre para mover',
7 'label': 'Trebello %1'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/he.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/he.js new file mode 100644 index 0000000..08da4df --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/he.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'he', {
6 'move': 'לחץ וגרור להזזה',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/hr.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/hr.js new file mode 100644 index 0000000..c0b8b31 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/hr.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'hr', {
6 'move': 'Klikni i povuci za pomicanje',
7 'label': '%1 widget'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/hu.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/hu.js new file mode 100644 index 0000000..597e11d --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/hu.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'hu', {
6 'move': 'Kattints és húzd a mozgatáshoz',
7 'label': '%1 modul'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/id.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/id.js new file mode 100644 index 0000000..b1cf39b --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/id.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'id', {
6 'move': 'Tekan dan geser untuk memindahkan',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/it.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/it.js new file mode 100644 index 0000000..707481f --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/it.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'it', {
6 'move': 'Fare clic e trascinare per spostare',
7 'label': 'Widget %1'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/ja.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/ja.js new file mode 100644 index 0000000..7c329d0 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/ja.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ja', {
6 'move': 'ドラッグして移動',
7 'label': '%1 ウィジェット'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/km.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/km.js new file mode 100644 index 0000000..08ec7aa --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/km.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'km', {
6 'move': 'ចុច​ហើយ​ទាញ​ដើម្បី​ផ្លាស់​ទី',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/ko.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/ko.js new file mode 100644 index 0000000..8c62287 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/ko.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ko', {
6 'move': '움직이려면 클릭 후 드래그 하세요',
7 'label': '%1 위젯'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/ku.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/ku.js new file mode 100644 index 0000000..2ff829b --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/ku.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ku', {
6 'move': 'کرتەبکە و ڕایبکێشە بۆ جوڵاندن',
7 'label': '%1 ویجێت'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/lv.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/lv.js new file mode 100644 index 0000000..de6167b --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/lv.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'lv', {
6 'move': 'Klikšķina un velc, lai pārvietotu',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/nb.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/nb.js new file mode 100644 index 0000000..dfba027 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/nb.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'nb', {
6 'move': 'Klikk og dra for å flytte',
7 'label': 'Widget %1'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/nl.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/nl.js new file mode 100644 index 0000000..188f48c --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/nl.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'nl', {
6 'move': 'Klik en sleep om te verplaatsen',
7 'label': '%1 widget'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/no.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/no.js new file mode 100644 index 0000000..62a93d9 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/no.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'no', {
6 'move': 'Klikk og dra for å flytte',
7 'label': 'Widget %1'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/oc.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/oc.js new file mode 100644 index 0000000..6a559e0 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/oc.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'oc', {
6 'move': 'Clicar e lisar per desplaçar',
7 'label': 'Element %1'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/pl.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/pl.js new file mode 100644 index 0000000..6486765 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/pl.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'pl', {
6 'move': 'Kliknij i przeciągnij, by przenieść.',
7 'label': 'Widget %1'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/pt-br.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/pt-br.js new file mode 100644 index 0000000..7f44722 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/pt-br.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'pt-br', {
6 'move': 'Click e arraste para mover',
7 'label': '%1 widget'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/pt.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/pt.js new file mode 100644 index 0000000..97ada9d --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/pt.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'pt', {
6 'move': 'Clique e arraste para mover',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/ru.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/ru.js new file mode 100644 index 0000000..68d612b --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/ru.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ru', {
6 'move': 'Нажмите и перетащите, чтобы переместить',
7 'label': '%1 виджет'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/sk.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/sk.js new file mode 100644 index 0000000..cd75ecb --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/sk.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sk', {
6 'move': 'Kliknite a potiahnite pre presunutie',
7 'label': '%1 widget'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/sl.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/sl.js new file mode 100644 index 0000000..64b8ae5 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/sl.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sl', {
6 'move': 'Kliknite in povlecite, da premaknete',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/sq.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/sq.js new file mode 100644 index 0000000..4c725c3 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/sq.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sq', {
6 'move': 'Kliko dhe tërhiqe për ta lëvizur',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/sv.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/sv.js new file mode 100644 index 0000000..a7e228f --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/sv.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sv', {
6 'move': 'Klicka och drag för att flytta',
7 'label': '%1-widget'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/tr.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/tr.js new file mode 100644 index 0000000..f25b1ea --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/tr.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'tr', {
6 'move': 'Taşımak için, tıklayın ve sürükleyin',
7 'label': '%1 Grafik Beleşeni'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/tt.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/tt.js new file mode 100644 index 0000000..1772db5 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/tt.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'tt', {
6 'move': 'Күчереп куер өчен басып шудырыгыз',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/ug.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/ug.js new file mode 100644 index 0000000..9f39366 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/ug.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ug', {
6 'move': 'يۆتكەشتە چېكىپ سۆرەڭ',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/uk.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/uk.js new file mode 100644 index 0000000..4eb2d2e --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/uk.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'uk', {
6 'move': 'Клікніть і потягніть для переміщення',
7 'label': '%1 віджет'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/vi.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/vi.js new file mode 100644 index 0000000..76f5680 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/vi.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'vi', {
6 'move': 'Nhấp chuột và kéo để di chuyển',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/zh-cn.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/zh-cn.js new file mode 100644 index 0000000..93f393c --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/zh-cn.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'zh-cn', {
6 'move': '点击并拖拽以移动',
7 'label': '%1 小部件'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/lang/zh.js b/app/assets/javascripts/ckeditor/plugins/widget/lang/zh.js new file mode 100644 index 0000000..a89a616 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/lang/zh.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'zh', {
6 'move': '拖曳以移動',
7 'label': '%1 小工具'
8} );
diff --git a/app/assets/javascripts/ckeditor/plugins/widget/plugin.js b/app/assets/javascripts/ckeditor/plugins/widget/plugin.js new file mode 100644 index 0000000..fbed1bf --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widget/plugin.js
@@ -0,0 +1,4126 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6/**
7 * @fileOverview [Widget](http://ckeditor.com/addon/widget) plugin.
8 */
9
10'use strict';
11
12( function() {
13 var DRAG_HANDLER_SIZE = 15;
14
15 CKEDITOR.plugins.add( 'widget', {
16 // jscs:disable maximumLineLength
17 lang: 'af,ar,az,bg,ca,cs,cy,da,de,de-ch,el,en,en-gb,eo,es,es-mx,eu,fa,fi,fr,gl,he,hr,hu,id,it,ja,km,ko,ku,lv,nb,nl,no,oc,pl,pt,pt-br,ru,sk,sl,sq,sv,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
18 // jscs:enable maximumLineLength
19 requires: 'lineutils,clipboard,widgetselection',
20 onLoad: function() {
21 CKEDITOR.addCss(
22 '.cke_widget_wrapper{' +
23 'position:relative;' +
24 'outline:none' +
25 '}' +
26 '.cke_widget_inline{' +
27 'display:inline-block' +
28 '}' +
29 '.cke_widget_wrapper:hover>.cke_widget_element{' +
30 'outline:2px solid yellow;' +
31 'cursor:default' +
32 '}' +
33 '.cke_widget_wrapper:hover .cke_widget_editable{' +
34 'outline:2px solid yellow' +
35 '}' +
36 '.cke_widget_wrapper.cke_widget_focused>.cke_widget_element,' +
37 // We need higher specificity than hover style.
38 '.cke_widget_wrapper .cke_widget_editable.cke_widget_editable_focused{' +
39 'outline:2px solid #ace' +
40 '}' +
41 '.cke_widget_editable{' +
42 'cursor:text' +
43 '}' +
44 '.cke_widget_drag_handler_container{' +
45 'position:absolute;' +
46 'width:' + DRAG_HANDLER_SIZE + 'px;' +
47 'height:0;' +
48 // Initially drag handler should not be visible, until its position will be
49 // calculated (http://dev.ckeditor.com/ticket/11177).
50 // We need to hide unpositined handlers, so they don't extend
51 // widget's outline far to the left (http://dev.ckeditor.com/ticket/12024).
52 'display:none;' +
53 'opacity:0.75;' +
54 'transition:height 0s 0.2s;' + // Delay hiding drag handler.
55 // Prevent drag handler from being misplaced (http://dev.ckeditor.com/ticket/11198).
56 'line-height:0' +
57 '}' +
58 '.cke_widget_wrapper:hover>.cke_widget_drag_handler_container{' +
59 'height:' + DRAG_HANDLER_SIZE + 'px;' +
60 'transition:none' +
61 '}' +
62 '.cke_widget_drag_handler_container:hover{' +
63 'opacity:1' +
64 '}' +
65 'img.cke_widget_drag_handler{' +
66 'cursor:move;' +
67 'width:' + DRAG_HANDLER_SIZE + 'px;' +
68 'height:' + DRAG_HANDLER_SIZE + 'px;' +
69 'display:inline-block' +
70 '}' +
71 '.cke_widget_mask{' +
72 'position:absolute;' +
73 'top:0;' +
74 'left:0;' +
75 'width:100%;' +
76 'height:100%;' +
77 'display:block' +
78 '}' +
79 '.cke_editable.cke_widget_dragging, .cke_editable.cke_widget_dragging *{' +
80 'cursor:move !important' +
81 '}'
82 );
83 },
84
85 beforeInit: function( editor ) {
86 /**
87 * An instance of widget repository. It contains all
88 * {@link CKEDITOR.plugins.widget.repository#registered registered widget definitions} and
89 * {@link CKEDITOR.plugins.widget.repository#instances initialized instances}.
90 *
91 * editor.widgets.add( 'someName', {
92 * // Widget definition...
93 * } );
94 *
95 * editor.widgets.registered.someName; // -> Widget definition
96 *
97 * @since 4.3
98 * @readonly
99 * @property {CKEDITOR.plugins.widget.repository} widgets
100 * @member CKEDITOR.editor
101 */
102 editor.widgets = new Repository( editor );
103 },
104
105 afterInit: function( editor ) {
106 addWidgetButtons( editor );
107 setupContextMenu( editor );
108 }
109 } );
110
111 /**
112 * Widget repository. It keeps track of all {@link #registered registered widget definitions} and
113 * {@link #instances initialized instances}. An instance of the repository is available under
114 * the {@link CKEDITOR.editor#widgets} property.
115 *
116 * @class CKEDITOR.plugins.widget.repository
117 * @mixins CKEDITOR.event
118 * @constructor Creates a widget repository instance. Note that the widget plugin automatically
119 * creates a repository instance which is available under the {@link CKEDITOR.editor#widgets} property.
120 * @param {CKEDITOR.editor} editor The editor instance for which the repository will be created.
121 */
122 function Repository( editor ) {
123 /**
124 * The editor instance for which this repository was created.
125 *
126 * @readonly
127 * @property {CKEDITOR.editor} editor
128 */
129 this.editor = editor;
130
131 /**
132 * A hash of registered widget definitions (definition name => {@link CKEDITOR.plugins.widget.definition}).
133 *
134 * To register a definition use the {@link #add} method.
135 *
136 * @readonly
137 */
138 this.registered = {};
139
140 /**
141 * An object containing initialized widget instances (widget id => {@link CKEDITOR.plugins.widget}).
142 *
143 * @readonly
144 */
145 this.instances = {};
146
147 /**
148 * An array of selected widget instances.
149 *
150 * @readonly
151 * @property {CKEDITOR.plugins.widget[]} selected
152 */
153 this.selected = [];
154
155 /**
156 * The focused widget instance. See also {@link CKEDITOR.plugins.widget#event-focus}
157 * and {@link CKEDITOR.plugins.widget#event-blur} events.
158 *
159 * editor.on( 'selectionChange', function() {
160 * if ( editor.widgets.focused ) {
161 * // Do something when a widget is focused...
162 * }
163 * } );
164 *
165 * @readonly
166 * @property {CKEDITOR.plugins.widget} focused
167 */
168 this.focused = null;
169
170 /**
171 * The widget instance that contains the nested editable which is currently focused.
172 *
173 * @readonly
174 * @property {CKEDITOR.plugins.widget} widgetHoldingFocusedEditable
175 */
176 this.widgetHoldingFocusedEditable = null;
177
178 this._ = {
179 nextId: 0,
180 upcasts: [],
181 upcastCallbacks: [],
182 filters: {}
183 };
184
185 setupWidgetsLifecycle( this );
186 setupSelectionObserver( this );
187 setupMouseObserver( this );
188 setupKeyboardObserver( this );
189 setupDragAndDrop( this );
190 setupNativeCutAndCopy( this );
191 }
192
193 Repository.prototype = {
194 /**
195 * Minimum interval between selection checks.
196 *
197 * @private
198 */
199 MIN_SELECTION_CHECK_INTERVAL: 500,
200
201 /**
202 * Adds a widget definition to the repository. Fires the {@link CKEDITOR.editor#widgetDefinition} event
203 * which allows to modify the widget definition which is going to be registered.
204 *
205 * @param {String} name The name of the widget definition.
206 * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget definition.
207 * @returns {CKEDITOR.plugins.widget.definition}
208 */
209 add: function( name, widgetDef ) {
210 // Create prototyped copy of original widget definition, so we won't modify it.
211 widgetDef = CKEDITOR.tools.prototypedCopy( widgetDef );
212 widgetDef.name = name;
213
214 widgetDef._ = widgetDef._ || {};
215
216 this.editor.fire( 'widgetDefinition', widgetDef );
217
218 if ( widgetDef.template )
219 widgetDef.template = new CKEDITOR.template( widgetDef.template );
220
221 addWidgetCommand( this.editor, widgetDef );
222 addWidgetProcessors( this, widgetDef );
223
224 this.registered[ name ] = widgetDef;
225
226 return widgetDef;
227 },
228
229 /**
230 * Adds a callback for element upcasting. Each callback will be executed
231 * for every element which is later tested by upcast methods. If a callback
232 * returns `false`, the element will not be upcasted.
233 *
234 * // Images with the "banner" class will not be upcasted (e.g. to the image widget).
235 * editor.widgets.addUpcastCallback( function( element ) {
236 * if ( element.name == 'img' && element.hasClass( 'banner' ) )
237 * return false;
238 * } );
239 *
240 * @param {Function} callback
241 * @param {CKEDITOR.htmlParser.element} callback.element
242 */
243 addUpcastCallback: function( callback ) {
244 this._.upcastCallbacks.push( callback );
245 },
246
247 /**
248 * Checks the selection to update widget states (selection and focus).
249 *
250 * This method is triggered by the {@link #event-checkSelection} event.
251 */
252 checkSelection: function() {
253 var sel = this.editor.getSelection(),
254 selectedElement = sel.getSelectedElement(),
255 updater = stateUpdater( this ),
256 widget;
257
258 // Widget is focused so commit and finish checking.
259 if ( selectedElement && ( widget = this.getByElement( selectedElement, true ) ) )
260 return updater.focus( widget ).select( widget ).commit();
261
262 var range = sel.getRanges()[ 0 ];
263
264 // No ranges or collapsed range mean that nothing is selected, so commit and finish checking.
265 if ( !range || range.collapsed )
266 return updater.commit();
267
268 // Range is not empty, so create walker checking for wrappers.
269 var walker = new CKEDITOR.dom.walker( range ),
270 wrapper;
271
272 walker.evaluator = Widget.isDomWidgetWrapper;
273
274 while ( ( wrapper = walker.next() ) )
275 updater.select( this.getByElement( wrapper ) );
276
277 updater.commit();
278 },
279
280 /**
281 * Checks if all widget instances are still present in the DOM.
282 * Destroys those instances that are not present.
283 * Reinitializes widgets on widget wrappers for which widget instances
284 * cannot be found. Takes nested widgets into account, too.
285 *
286 * This method triggers the {@link #event-checkWidgets} event whose listeners
287 * can cancel the method's execution or modify its options.
288 *
289 * @param [options] The options object.
290 * @param {Boolean} [options.initOnlyNew] Initializes widgets only on newly wrapped
291 * widget elements (those which still have the `cke_widget_new` class). When this option is
292 * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure)
293 * will not be reinitialized. This makes the check faster.
294 * @param {Boolean} [options.focusInited] If only one widget is initialized by
295 * the method, it will be focused.
296 */
297 checkWidgets: function( options ) {
298 this.fire( 'checkWidgets', CKEDITOR.tools.copy( options || {} ) );
299 },
300
301 /**
302 * Removes the widget from the editor and moves the selection to the closest
303 * editable position if the widget was focused before.
304 *
305 * @param {CKEDITOR.plugins.widget} widget The widget instance to be deleted.
306 */
307 del: function( widget ) {
308 if ( this.focused === widget ) {
309 var editor = widget.editor,
310 range = editor.createRange(),
311 found;
312
313 // If haven't found place for caret on the default side,
314 // try to find it on the other side.
315 if ( !( found = range.moveToClosestEditablePosition( widget.wrapper, true ) ) )
316 found = range.moveToClosestEditablePosition( widget.wrapper, false );
317
318 if ( found )
319 editor.getSelection().selectRanges( [ range ] );
320 }
321
322 widget.wrapper.remove();
323 this.destroy( widget, true );
324 },
325
326 /**
327 * Destroys the widget instance and all its nested widgets (widgets inside its nested editables).
328 *
329 * @param {CKEDITOR.plugins.widget} widget The widget instance to be destroyed.
330 * @param {Boolean} [offline] Whether the widget is offline (detached from the DOM tree) &mdash;
331 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
332 */
333 destroy: function( widget, offline ) {
334 if ( this.widgetHoldingFocusedEditable === widget )
335 setFocusedEditable( this, widget, null, offline );
336
337 widget.destroy( offline );
338 delete this.instances[ widget.id ];
339 this.fire( 'instanceDestroyed', widget );
340 },
341
342 /**
343 * Destroys all widget instances.
344 *
345 * @param {Boolean} [offline] Whether the widgets are offline (detached from the DOM tree) &mdash;
346 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
347 * @param {CKEDITOR.dom.element} [container] The container within widgets will be destroyed.
348 * This option will be ignored if the `offline` flag was set to `true`, because in such case
349 * it is not possible to find widgets within the passed block.
350 */
351 destroyAll: function( offline, container ) {
352 var widget,
353 id,
354 instances = this.instances;
355
356 if ( container && !offline ) {
357 var wrappers = container.find( '.cke_widget_wrapper' ),
358 l = wrappers.count(),
359 i = 0;
360
361 // Length is constant, because this is not a live node list.
362 // Note: since querySelectorAll returns nodes in document order,
363 // outer widgets are always placed before their nested widgets and therefore
364 // are destroyed before them.
365 for ( ; i < l; ++i ) {
366 widget = this.getByElement( wrappers.getItem( i ), true );
367 // Widget might not be found, because it could be a nested widget,
368 // which would be destroyed when destroying its parent.
369 if ( widget )
370 this.destroy( widget );
371 }
372
373 return;
374 }
375
376 for ( id in instances ) {
377 widget = instances[ id ];
378 this.destroy( widget, offline );
379 }
380 },
381
382 /**
383 * Finalizes a process of widget creation. This includes:
384 *
385 * * inserting widget element into editor,
386 * * marking widget instance as ready (see {@link CKEDITOR.plugins.widget#event-ready}),
387 * * focusing widget instance.
388 *
389 * This method is used by the default widget's command and is called
390 * after widget's dialog (if set) is closed. It may also be used in a
391 * customized process of widget creation and insertion.
392 *
393 * widget.once( 'edit', function() {
394 * // Finalize creation only of not ready widgets.
395 * if ( widget.isReady() )
396 * return;
397 *
398 * // Cancel edit event to prevent automatic widget insertion.
399 * evt.cancel();
400 *
401 * CustomDialog.open( widget.data, function saveCallback( savedData ) {
402 * // Cache the container, because widget may be destroyed while saving data,
403 * // if this process will require some deep transformations.
404 * var container = widget.wrapper.getParent();
405 *
406 * widget.setData( savedData );
407 *
408 * // Widget will be retrieved from container and inserted into editor.
409 * editor.widgets.finalizeCreation( container );
410 * } );
411 * } );
412 *
413 * @param {CKEDITOR.dom.element/CKEDITOR.dom.documentFragment} container The element
414 * or document fragment which contains widget wrapper. The container is used, so before
415 * finalizing creation the widget can be freely transformed (even destroyed and reinitialized).
416 */
417 finalizeCreation: function( container ) {
418 var wrapper = container.getFirst();
419 if ( wrapper && Widget.isDomWidgetWrapper( wrapper ) ) {
420 this.editor.insertElement( wrapper );
421
422 var widget = this.getByElement( wrapper );
423 // Fire postponed #ready event.
424 widget.ready = true;
425 widget.fire( 'ready' );
426 widget.focus();
427 }
428 },
429
430 /**
431 * Finds a widget instance which contains a given element. The element will be the {@link CKEDITOR.plugins.widget#wrapper wrapper}
432 * of the returned widget or a descendant of this {@link CKEDITOR.plugins.widget#wrapper wrapper}.
433 *
434 * editor.widgets.getByElement( someWidget.wrapper ); // -> someWidget
435 * editor.widgets.getByElement( someWidget.parts.caption ); // -> someWidget
436 *
437 * // Check wrapper only:
438 * editor.widgets.getByElement( someWidget.wrapper, true ); // -> someWidget
439 * editor.widgets.getByElement( someWidget.parts.caption, true ); // -> null
440 *
441 * @param {CKEDITOR.dom.element} element The element to be checked.
442 * @param {Boolean} [checkWrapperOnly] If set to `true`, the method will not check wrappers' descendants.
443 * @returns {CKEDITOR.plugins.widget} The widget instance or `null`.
444 */
445 getByElement: ( function() {
446 var validWrapperElements = { div: 1, span: 1 };
447 function getWidgetId( element ) {
448 return element.is( validWrapperElements ) && element.data( 'cke-widget-id' );
449 }
450
451 return function( element, checkWrapperOnly ) {
452 if ( !element )
453 return null;
454
455 var id = getWidgetId( element );
456
457 // There's no need to check element parents if element is a wrapper.
458 if ( !checkWrapperOnly && !id ) {
459 var limit = this.editor.editable();
460
461 // Try to find a closest ascendant which is a widget wrapper.
462 do {
463 element = element.getParent();
464 } while ( element && !element.equals( limit ) && !( id = getWidgetId( element ) ) );
465 }
466
467 return this.instances[ id ] || null;
468 };
469 } )(),
470
471 /**
472 * Initializes a widget on a given element if the widget has not been initialized on it yet.
473 *
474 * @param {CKEDITOR.dom.element} element The future widget element.
475 * @param {String/CKEDITOR.plugins.widget.definition} [widgetDef] Name of a widget or a widget definition.
476 * The widget definition should be previously registered by using the
477 * {@link CKEDITOR.plugins.widget.repository#add} method.
478 * @param [startupData] Widget startup data (has precedence over default one).
479 * @returns {CKEDITOR.plugins.widget} The widget instance or `null` if a widget could not be initialized on
480 * a given element.
481 */
482 initOn: function( element, widgetDef, startupData ) {
483 if ( !widgetDef )
484 widgetDef = this.registered[ element.data( 'widget' ) ];
485 else if ( typeof widgetDef == 'string' )
486 widgetDef = this.registered[ widgetDef ];
487
488 if ( !widgetDef )
489 return null;
490
491 // Wrap element if still wasn't wrapped (was added during runtime by method that skips dataProcessor).
492 var wrapper = this.wrapElement( element, widgetDef.name );
493
494 if ( wrapper ) {
495 // Check if widget wrapper is new (widget hasn't been initialized on it yet).
496 // This class will be removed by widget constructor to avoid locking snapshot twice.
497 if ( wrapper.hasClass( 'cke_widget_new' ) ) {
498 var widget = new Widget( this, this._.nextId++, element, widgetDef, startupData );
499
500 // Widget could be destroyed when initializing it.
501 if ( widget.isInited() ) {
502 this.instances[ widget.id ] = widget;
503
504 return widget;
505 } else {
506 return null;
507 }
508 }
509
510 // Widget already has been initialized, so try to get widget by element.
511 // Note - it may happen that other instance will returned than the one created above,
512 // if for example widget was destroyed and reinitialized.
513 return this.getByElement( element );
514 }
515
516 // No wrapper means that there's no widget for this element.
517 return null;
518 },
519
520 /**
521 * Initializes widgets on all elements which were wrapped by {@link #wrapElement} and
522 * have not been initialized yet.
523 *
524 * @param {CKEDITOR.dom.element} [container=editor.editable()] The container which will be checked for not
525 * initialized widgets. Defaults to editor's {@link CKEDITOR.editor#editable editable} element.
526 * @returns {CKEDITOR.plugins.widget[]} Array of widget instances which have been initialized.
527 * Note: Only first-level widgets are returned &mdash; without nested widgets.
528 */
529 initOnAll: function( container ) {
530 var newWidgets = ( container || this.editor.editable() ).find( '.cke_widget_new' ),
531 newInstances = [],
532 instance;
533
534 for ( var i = newWidgets.count(); i--; ) {
535 instance = this.initOn( newWidgets.getItem( i ).getFirst( Widget.isDomWidgetElement ) );
536 if ( instance )
537 newInstances.push( instance );
538 }
539
540 return newInstances;
541 },
542
543 /**
544 * Allows to listen to events on specific types of widgets, even if they are not created yet.
545 *
546 * Please note that this method inherits parameters from the {@link CKEDITOR.event#method-on} method with one
547 * extra parameter at the beginning which is the widget name.
548 *
549 * editor.widgets.onWidget( 'image', 'action', function( evt ) {
550 * // Event `action` occurs on `image` widget.
551 * } );
552 *
553 * @since 4.5
554 * @param {String} widgetName
555 * @param {String} eventName
556 * @param {Function} listenerFunction
557 * @param {Object} [scopeObj]
558 * @param {Object} [listenerData]
559 * @param {Number} [priority=10]
560 */
561 onWidget: function( widgetName ) {
562 var args = Array.prototype.slice.call( arguments );
563
564 args.shift();
565
566 for ( var i in this.instances ) {
567 var instance = this.instances[ i ];
568
569 if ( instance.name == widgetName ) {
570 instance.on.apply( instance, args );
571 }
572 }
573
574 this.on( 'instanceCreated', function( evt ) {
575 var widget = evt.data;
576
577 if ( widget.name == widgetName ) {
578 widget.on.apply( widget, args );
579 }
580 } );
581 },
582
583 /**
584 * Parses element classes string and returns an object
585 * whose keys contain class names. Skips all `cke_*` classes.
586 *
587 * This method is used by the {@link CKEDITOR.plugins.widget#getClasses} method and
588 * may be used when overriding that method.
589 *
590 * @since 4.4
591 * @param {String} classes String (value of `class` attribute).
592 * @returns {Object} Object containing classes or `null` if no classes found.
593 */
594 parseElementClasses: function( classes ) {
595 if ( !classes )
596 return null;
597
598 classes = CKEDITOR.tools.trim( classes ).split( /\s+/ );
599
600 var cl,
601 obj = {},
602 hasClasses = 0;
603
604 while ( ( cl = classes.pop() ) ) {
605 if ( cl.indexOf( 'cke_' ) == -1 )
606 obj[ cl ] = hasClasses = 1;
607 }
608
609 return hasClasses ? obj : null;
610 },
611
612 /**
613 * Wraps an element with a widget's non-editable container.
614 *
615 * If this method is called on an {@link CKEDITOR.htmlParser.element}, then it will
616 * also take care of fixing the DOM after wrapping (the wrapper may not be allowed in element's parent).
617 *
618 * @param {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} element The widget element to be wrapped.
619 * @param {String} [widgetName] The name of the widget definition. Defaults to element's `data-widget`
620 * attribute value.
621 * @returns {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} The wrapper element or `null` if
622 * the widget definition of this name is not registered.
623 */
624 wrapElement: function( element, widgetName ) {
625 var wrapper = null,
626 widgetDef,
627 isInline;
628
629 if ( element instanceof CKEDITOR.dom.element ) {
630 widgetName = widgetName || element.data( 'widget' );
631 widgetDef = this.registered[ widgetName ];
632
633 if ( !widgetDef )
634 return null;
635
636 // Do not wrap already wrapped element.
637 wrapper = element.getParent();
638 if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.data( 'cke-widget-wrapper' ) )
639 return wrapper;
640
641 // If attribute isn't already set (e.g. for pasted widget), set it.
642 if ( !element.hasAttribute( 'data-cke-widget-keep-attr' ) )
643 element.data( 'cke-widget-keep-attr', element.data( 'widget' ) ? 1 : 0 );
644
645 element.data( 'widget', widgetName );
646
647 isInline = isWidgetInline( widgetDef, element.getName() );
648
649 wrapper = new CKEDITOR.dom.element( isInline ? 'span' : 'div' );
650 wrapper.setAttributes( getWrapperAttributes( isInline, widgetName ) );
651
652 wrapper.data( 'cke-display-name', widgetDef.pathName ? widgetDef.pathName : element.getName() );
653
654 // Replace element unless it is a detached one.
655 if ( element.getParent( true ) )
656 wrapper.replace( element );
657 element.appendTo( wrapper );
658 }
659 else if ( element instanceof CKEDITOR.htmlParser.element ) {
660 widgetName = widgetName || element.attributes[ 'data-widget' ];
661 widgetDef = this.registered[ widgetName ];
662
663 if ( !widgetDef )
664 return null;
665
666 wrapper = element.parent;
667 if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.attributes[ 'data-cke-widget-wrapper' ] )
668 return wrapper;
669
670 // If attribute isn't already set (e.g. for pasted widget), set it.
671 if ( !( 'data-cke-widget-keep-attr' in element.attributes ) )
672 element.attributes[ 'data-cke-widget-keep-attr' ] = element.attributes[ 'data-widget' ] ? 1 : 0;
673 if ( widgetName )
674 element.attributes[ 'data-widget' ] = widgetName;
675
676 isInline = isWidgetInline( widgetDef, element.name );
677
678 wrapper = new CKEDITOR.htmlParser.element( isInline ? 'span' : 'div', getWrapperAttributes( isInline, widgetName ) );
679 wrapper.attributes[ 'data-cke-display-name' ] = widgetDef.pathName ? widgetDef.pathName : element.name;
680
681 var parent = element.parent,
682 index;
683
684 // Don't detach already detached element.
685 if ( parent ) {
686 index = element.getIndex();
687 element.remove();
688 }
689
690 wrapper.add( element );
691
692 // Insert wrapper fixing DOM (splitting parents if wrapper is not allowed inside them).
693 parent && insertElement( parent, index, wrapper );
694 }
695
696 return wrapper;
697 },
698
699 // Expose for tests.
700 _tests_createEditableFilter: createEditableFilter
701 };
702
703 CKEDITOR.event.implementOn( Repository.prototype );
704
705 /**
706 * An event fired when a widget instance is created, but before it is fully initialized.
707 *
708 * @event instanceCreated
709 * @param {CKEDITOR.plugins.widget} data The widget instance.
710 */
711
712 /**
713 * An event fired when a widget instance was destroyed.
714 *
715 * See also {@link CKEDITOR.plugins.widget#event-destroy}.
716 *
717 * @event instanceDestroyed
718 * @param {CKEDITOR.plugins.widget} data The widget instance.
719 */
720
721 /**
722 * An event fired to trigger the selection check.
723 *
724 * See the {@link #method-checkSelection} method.
725 *
726 * @event checkSelection
727 */
728
729 /**
730 * An event fired by the the {@link #method-checkWidgets} method.
731 *
732 * It can be canceled in order to stop the {@link #method-checkWidgets}
733 * method execution or the event listener can modify the method's options.
734 *
735 * @event checkWidgets
736 * @param [data]
737 * @param {Boolean} [data.initOnlyNew] Initialize widgets only on newly wrapped
738 * widget elements (those which still have the `cke_widget_new` class). When this option is
739 * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure)
740 * will not be reinitialized. This makes the check faster.
741 * @param {Boolean} [data.focusInited] If only one widget is initialized by
742 * the method, it will be focused.
743 */
744
745
746 /**
747 * An instance of a widget. Together with {@link CKEDITOR.plugins.widget.repository} these
748 * two classes constitute the core of the Widget System.
749 *
750 * Note that neither the repository nor the widget instances can be created by using their constructors.
751 * A repository instance is automatically set up by the Widget plugin and is accessible under
752 * {@link CKEDITOR.editor#widgets}, while widget instances are created and destroyed by the repository.
753 *
754 * To create a widget, first you need to {@link CKEDITOR.plugins.widget.repository#add register} its
755 * {@link CKEDITOR.plugins.widget.definition definition}:
756 *
757 * editor.widgets.add( 'simplebox', {
758 * upcast: function( element ) {
759 * // Defines which elements will become widgets.
760 * if ( element.hasClass( 'simplebox' ) )
761 * return true;
762 * },
763 * init: function() {
764 * // ...
765 * }
766 * } );
767 *
768 * Once the widget definition is registered, widgets will be automatically
769 * created when loading data:
770 *
771 * editor.setData( '<div class="simplebox">foo</div>', function() {
772 * console.log( editor.widgets.instances ); // -> An object containing one instance.
773 * } );
774 *
775 * It is also possible to create instances during runtime by using a command
776 * (if a {@link CKEDITOR.plugins.widget.definition#template} property was defined):
777 *
778 * // You can execute an automatically defined command to
779 * // insert a new simplebox widget or edit the one currently focused.
780 * editor.execCommand( 'simplebox' );
781 *
782 * Note: Since CKEditor 4.5 widget's `startupData` can be passed as the command argument:
783 *
784 * editor.execCommand( 'simplebox', {
785 * startupData: {
786 * align: 'left'
787 * }
788 * } );
789 *
790 * A widget can also be created in a completely custom way:
791 *
792 * var element = editor.document.createElement( 'div' );
793 * editor.insertElement( element );
794 * var widget = editor.widgets.initOn( element, 'simplebox' );
795 *
796 * @since 4.3
797 * @class CKEDITOR.plugins.widget
798 * @mixins CKEDITOR.event
799 * @extends CKEDITOR.plugins.widget.definition
800 * @constructor Creates an instance of the widget class. Do not use it directly, but instead initialize widgets
801 * by using the {@link CKEDITOR.plugins.widget.repository#initOn} method or by the upcasting system.
802 * @param {CKEDITOR.plugins.widget.repository} widgetsRepo
803 * @param {Number} id Unique ID of this widget instance.
804 * @param {CKEDITOR.dom.element} element The widget element.
805 * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget's registered definition.
806 * @param [startupData] Initial widget data. This data object will overwrite the default data and
807 * the data loaded from the DOM.
808 */
809 function Widget( widgetsRepo, id, element, widgetDef, startupData ) {
810 var editor = widgetsRepo.editor;
811
812 // Extend this widget with widgetDef-specific methods and properties.
813 CKEDITOR.tools.extend( this, widgetDef, {
814 /**
815 * The editor instance.
816 *
817 * @readonly
818 * @property {CKEDITOR.editor}
819 */
820 editor: editor,
821
822 /**
823 * This widget's unique (per editor instance) ID.
824 *
825 * @readonly
826 * @property {Number}
827 */
828 id: id,
829
830 /**
831 * Whether this widget is an inline widget (based on an inline element unless
832 * forced otherwise by {@link CKEDITOR.plugins.widget.definition#inline}).
833 *
834 * **Note:** This option does not allow to turn a block element into an inline widget.
835 * However, it makes it possible to turn an inline element into a block widget or to
836 * force a correct type in case when automatic recognition fails.
837 *
838 * @readonly
839 * @property {Boolean}
840 */
841 inline: element.getParent().getName() == 'span',
842
843 /**
844 * The widget element &mdash; the element on which the widget was initialized.
845 *
846 * @readonly
847 * @property {CKEDITOR.dom.element} element
848 */
849 element: element,
850
851 /**
852 * Widget's data object.
853 *
854 * The data can only be set by using the {@link #setData} method.
855 * Changes made to the data fire the {@link #event-data} event.
856 *
857 * @readonly
858 */
859 data: CKEDITOR.tools.extend( {}, typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults ),
860
861 /**
862 * Indicates if a widget is data-ready. Set to `true` when data from all sources
863 * ({@link CKEDITOR.plugins.widget.definition#defaults}, set in the
864 * {@link #init} method, loaded from the widget's element and startup data coming from the constructor)
865 * are finally loaded. This is immediately followed by the first {@link #event-data}.
866 *
867 * @readonly
868 */
869 dataReady: false,
870
871 /**
872 * Whether a widget instance was initialized. This means that:
873 *
874 * * An instance was created,
875 * * Its properties were set,
876 * * The `init` method was executed.
877 *
878 * **Note**: The first {@link #event-data} event could not be fired yet which
879 * means that the widget's DOM has not been set up yet. Wait for the {@link #event-ready}
880 * event to be notified when a widget is fully initialized and ready.
881 *
882 * **Note**: Use the {@link #isInited} method to check whether a widget is initialized and
883 * has not been destroyed.
884 *
885 * @readonly
886 */
887 inited: false,
888
889 /**
890 * Whether a widget instance is ready. This means that the widget is {@link #inited} and
891 * that its DOM was finally set up.
892 *
893 * **Note:** Use the {@link #isReady} method to check whether a widget is ready and
894 * has not been destroyed.
895 *
896 * @readonly
897 */
898 ready: false,
899
900 // Revert what widgetDef could override (automatic #edit listener).
901 edit: Widget.prototype.edit,
902
903 /**
904 * The nested editable element which is currently focused.
905 *
906 * @readonly
907 * @property {CKEDITOR.plugins.widget.nestedEditable}
908 */
909 focusedEditable: null,
910
911 /**
912 * The widget definition from which this instance was created.
913 *
914 * @readonly
915 * @property {CKEDITOR.plugins.widget.definition} definition
916 */
917 definition: widgetDef,
918
919 /**
920 * Link to the widget repository which created this instance.
921 *
922 * @readonly
923 * @property {CKEDITOR.plugins.widget.repository} repository
924 */
925 repository: widgetsRepo,
926
927 draggable: widgetDef.draggable !== false,
928
929 // WAAARNING: Overwrite widgetDef's priv object, because otherwise violent unicorn's gonna visit you.
930 _: {
931 downcastFn: ( widgetDef.downcast && typeof widgetDef.downcast == 'string' ) ?
932 widgetDef.downcasts[ widgetDef.downcast ] : widgetDef.downcast
933 }
934 }, true );
935
936 /**
937 * An object of widget component elements.
938 *
939 * For every `partName => selector` pair in {@link CKEDITOR.plugins.widget.definition#parts},
940 * one `partName => element` pair is added to this object during the widget initialization.
941 *
942 * @readonly
943 * @property {Object} parts
944 */
945
946 /**
947 * The template which will be used to create a new widget element (when the widget's command is executed).
948 * It will be populated with {@link #defaults default values}.
949 *
950 * @readonly
951 * @property {CKEDITOR.template} template
952 */
953
954 /**
955 * The widget wrapper &mdash; a non-editable `div` or `span` element (depending on {@link #inline})
956 * which is a parent of the {@link #element} and widget compontents like the drag handler and the {@link #mask}.
957 * It is the outermost widget element.
958 *
959 * @readonly
960 * @property {CKEDITOR.dom.element} wrapper
961 */
962
963 widgetsRepo.fire( 'instanceCreated', this );
964
965 setupWidget( this, widgetDef );
966
967 this.init && this.init();
968
969 // Finally mark widget as inited.
970 this.inited = true;
971
972 setupWidgetData( this, startupData );
973
974 // If at some point (e.g. in #data listener) widget hasn't been destroyed
975 // and widget is already attached to document then fire #ready.
976 if ( this.isInited() && editor.editable().contains( this.wrapper ) ) {
977 this.ready = true;
978 this.fire( 'ready' );
979 }
980 }
981
982 Widget.prototype = {
983 /**
984 * Adds a class to the widget element. This method is used by
985 * the {@link #applyStyle} method and should be overridden by widgets
986 * which should handle classes differently (e.g. add them to other elements).
987 *
988 * Since 4.6.0 this method also adds a corresponding class prefixed with {@link #WRAPPER_CLASS_PREFIX}
989 * to the widget wrapper element.
990 *
991 * **Note**: This method should not be used directly. Use the {@link #setData} method to
992 * set the `classes` property. Read more in the {@link #setData} documentation.
993 *
994 * See also: {@link #removeClass}, {@link #hasClass}, {@link #getClasses}.
995 *
996 * @since 4.4
997 * @param {String} className The class name to be added.
998 */
999 addClass: function( className ) {
1000 this.element.addClass( className );
1001 this.wrapper.addClass( Widget.WRAPPER_CLASS_PREFIX + className );
1002 },
1003
1004 /**
1005 * Applies the specified style to the widget. It is highly recommended to use the
1006 * {@link CKEDITOR.editor#applyStyle} or {@link CKEDITOR.style#apply} methods instead of
1007 * using this method directly, because unlike editor's and style's methods, this one
1008 * does not perform any checks.
1009 *
1010 * By default this method handles only classes defined in the style. It clones existing
1011 * classes which are stored in the {@link #property-data widget data}'s `classes` property,
1012 * adds new classes, and calls the {@link #setData} method if at least one new class was added.
1013 * Then, using the {@link #event-data} event listener widget applies modifications passing
1014 * new classes to the {@link #addClass} method.
1015 *
1016 * If you need to handle classes differently than in the default way, you can override the
1017 * {@link #addClass} and related methods. You can also handle other style properties than `classes`
1018 * by overriding this method.
1019 *
1020 * See also: {@link #checkStyleActive}, {@link #removeStyle}.
1021 *
1022 * @since 4.4
1023 * @param {CKEDITOR.style} style The custom widget style to be applied.
1024 */
1025 applyStyle: function( style ) {
1026 applyRemoveStyle( this, style, 1 );
1027 },
1028
1029 /**
1030 * Checks if the specified style is applied to this widget. It is highly recommended to use the
1031 * {@link CKEDITOR.style#checkActive} method instead of using this method directly,
1032 * because unlike style's method, this one does not perform any checks.
1033 *
1034 * By default this method handles only classes defined in the style and passes
1035 * them to the {@link #hasClass} method. You can override these methods to handle classes
1036 * differently or to handle more of the style properties.
1037 *
1038 * See also: {@link #applyStyle}, {@link #removeStyle}.
1039 *
1040 * @since 4.4
1041 * @param {CKEDITOR.style} style The custom widget style to be checked.
1042 * @returns {Boolean} Whether the style is applied to this widget.
1043 */
1044 checkStyleActive: function( style ) {
1045 var classes = getStyleClasses( style ),
1046 cl;
1047
1048 if ( !classes )
1049 return false;
1050
1051 while ( ( cl = classes.pop() ) ) {
1052 if ( !this.hasClass( cl ) )
1053 return false;
1054 }
1055 return true;
1056 },
1057
1058 /**
1059 * Destroys this widget instance.
1060 *
1061 * Use {@link CKEDITOR.plugins.widget.repository#destroy} when possible instead of this method.
1062 *
1063 * This method fires the {#event-destroy} event.
1064 *
1065 * @param {Boolean} [offline] Whether a widget is offline (detached from the DOM tree) &mdash;
1066 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
1067 */
1068 destroy: function( offline ) {
1069 this.fire( 'destroy' );
1070
1071 if ( this.editables ) {
1072 for ( var name in this.editables )
1073 this.destroyEditable( name, offline );
1074 }
1075
1076 if ( !offline ) {
1077 if ( this.element.data( 'cke-widget-keep-attr' ) == '0' )
1078 this.element.removeAttribute( 'data-widget' );
1079 this.element.removeAttributes( [ 'data-cke-widget-data', 'data-cke-widget-keep-attr' ] );
1080 this.element.removeClass( 'cke_widget_element' );
1081 this.element.replace( this.wrapper );
1082 }
1083
1084 this.wrapper = null;
1085 },
1086
1087 /**
1088 * Destroys a nested editable and all nested widgets.
1089 *
1090 * @param {String} editableName Nested editable name.
1091 * @param {Boolean} [offline] See {@link #method-destroy} method.
1092 */
1093 destroyEditable: function( editableName, offline ) {
1094 var editable = this.editables[ editableName ];
1095
1096 editable.removeListener( 'focus', onEditableFocus );
1097 editable.removeListener( 'blur', onEditableBlur );
1098 this.editor.focusManager.remove( editable );
1099
1100 if ( !offline ) {
1101 this.repository.destroyAll( false, editable );
1102 editable.removeClass( 'cke_widget_editable' );
1103 editable.removeClass( 'cke_widget_editable_focused' );
1104 editable.removeAttributes( [ 'contenteditable', 'data-cke-widget-editable', 'data-cke-enter-mode' ] );
1105 }
1106
1107 delete this.editables[ editableName ];
1108 },
1109
1110 /**
1111 * Starts widget editing.
1112 *
1113 * This method fires the {@link CKEDITOR.plugins.widget#event-edit} event
1114 * which may be canceled in order to prevent it from opening a dialog window.
1115 *
1116 * The dialog window name is obtained from the event's data `dialog` property or
1117 * from {@link CKEDITOR.plugins.widget.definition#dialog}.
1118 *
1119 * @returns {Boolean} Returns `true` if a dialog window was opened.
1120 */
1121 edit: function() {
1122 var evtData = { dialog: this.dialog },
1123 that = this;
1124
1125 // Edit event was blocked or there's no dialog to be automatically opened.
1126 if ( this.fire( 'edit', evtData ) === false || !evtData.dialog )
1127 return false;
1128
1129 this.editor.openDialog( evtData.dialog, function( dialog ) {
1130 var showListener,
1131 okListener;
1132
1133 // Allow to add a custom dialog handler.
1134 if ( that.fire( 'dialog', dialog ) === false )
1135 return;
1136
1137 showListener = dialog.on( 'show', function() {
1138 dialog.setupContent( that );
1139 } );
1140
1141 okListener = dialog.on( 'ok', function() {
1142 // Commit dialog's fields, but prevent from
1143 // firing data event for every field. Fire only one,
1144 // bulk event at the end.
1145 var dataChanged,
1146 dataListener = that.on( 'data', function( evt ) {
1147 dataChanged = 1;
1148 evt.cancel();
1149 }, null, null, 0 );
1150
1151 // Create snapshot preceeding snapshot with changed widget...
1152 // TODO it should not be required, but it is and I found similar
1153 // code in dialog#ok listener in dialog/plugin.js.
1154 that.editor.fire( 'saveSnapshot' );
1155 dialog.commitContent( that );
1156
1157 dataListener.removeListener();
1158 if ( dataChanged ) {
1159 that.fire( 'data', that.data );
1160 that.editor.fire( 'saveSnapshot' );
1161 }
1162 } );
1163
1164 dialog.once( 'hide', function() {
1165 showListener.removeListener();
1166 okListener.removeListener();
1167 } );
1168 } );
1169
1170 return true;
1171 },
1172
1173 /**
1174 * Returns widget element classes parsed to an object. This method
1175 * is used to populate the `classes` property of widget's {@link #property-data}.
1176 *
1177 * This method reuses {@link CKEDITOR.plugins.widget.repository#parseElementClasses}.
1178 * It should be overriden if a widget should handle classes differently (e.g. on other elements).
1179 *
1180 * See also: {@link #removeClass}, {@link #addClass}, {@link #hasClass}.
1181 *
1182 * @since 4.4
1183 * @returns {Object}
1184 */
1185 getClasses: function() {
1186 return this.repository.parseElementClasses( this.element.getAttribute( 'class' ) );
1187 },
1188
1189 /**
1190 * Checks if the widget element has specified class. This method is used by
1191 * the {@link #checkStyleActive} method and should be overriden by widgets
1192 * which should handle classes differently (e.g. on other elements).
1193 *
1194 * See also: {@link #removeClass}, {@link #addClass}, {@link #getClasses}.
1195 *
1196 * @since 4.4
1197 * @param {String} className The class to be checked.
1198 * @param {Boolean} Whether a widget has specified class.
1199 */
1200 hasClass: function( className ) {
1201 return this.element.hasClass( className );
1202 },
1203
1204 /**
1205 * Initializes a nested editable.
1206 *
1207 * **Note**: Only elements from {@link CKEDITOR.dtd#$editable} may become editables.
1208 *
1209 * @param {String} editableName The nested editable name.
1210 * @param {CKEDITOR.plugins.widget.nestedEditable.definition} definition The definition of the nested editable.
1211 * @returns {Boolean} Whether an editable was successfully initialized.
1212 */
1213 initEditable: function( editableName, definition ) {
1214 // Don't fetch just first element which matched selector but look for a correct one. (http://dev.ckeditor.com/ticket/13334)
1215 var editable = this._findOneNotNested( definition.selector );
1216
1217 if ( editable && editable.is( CKEDITOR.dtd.$editable ) ) {
1218 editable = new NestedEditable( this.editor, editable, {
1219 filter: createEditableFilter.call( this.repository, this.name, editableName, definition )
1220 } );
1221 this.editables[ editableName ] = editable;
1222
1223 editable.setAttributes( {
1224 contenteditable: 'true',
1225 'data-cke-widget-editable': editableName,
1226 'data-cke-enter-mode': editable.enterMode
1227 } );
1228
1229 if ( editable.filter )
1230 editable.data( 'cke-filter', editable.filter.id );
1231
1232 editable.addClass( 'cke_widget_editable' );
1233 // This class may be left when d&ding widget which
1234 // had focused editable. Clean this class here, not in
1235 // cleanUpWidgetElement for performance and code size reasons.
1236 editable.removeClass( 'cke_widget_editable_focused' );
1237
1238 if ( definition.pathName )
1239 editable.data( 'cke-display-name', definition.pathName );
1240
1241 this.editor.focusManager.add( editable );
1242 editable.on( 'focus', onEditableFocus, this );
1243 CKEDITOR.env.ie && editable.on( 'blur', onEditableBlur, this );
1244
1245 // Finally, process editable's data. This data wasn't processed when loading
1246 // editor's data, becuase they need to be processed separately, with its own filters and settings.
1247 editable._.initialSetData = true;
1248 editable.setData( editable.getHtml() );
1249
1250 return true;
1251 }
1252
1253 return false;
1254 },
1255
1256 /**
1257 * Looks inside wrapper element to find a node that
1258 * matches given selector and is not nested in other widget. (http://dev.ckeditor.com/ticket/13334)
1259 *
1260 * @since 4.5
1261 * @private
1262 * @param {String} selector Selector to match.
1263 * @returns {CKEDITOR.dom.element} Matched element or `null` if a node has not been found.
1264 */
1265 _findOneNotNested: function( selector ) {
1266 var matchedElements = this.wrapper.find( selector ),
1267 match,
1268 closestWrapper;
1269
1270 for ( var i = 0; i < matchedElements.count(); i++ ) {
1271 match = matchedElements.getItem( i );
1272 closestWrapper = match.getAscendant( Widget.isDomWidgetWrapper );
1273
1274 // The closest ascendant-wrapper of this match defines to which widget
1275 // this match belongs. If the ascendant is this widget's wrapper
1276 // it means that the match is not nested in other widget.
1277 if ( this.wrapper.equals( closestWrapper ) ) {
1278 return match;
1279 }
1280 }
1281
1282 return null;
1283 },
1284
1285 /**
1286 * Checks if a widget has already been initialized and has not been destroyed yet.
1287 *
1288 * See {@link #inited} for more details.
1289 *
1290 * @returns {Boolean}
1291 */
1292 isInited: function() {
1293 return !!( this.wrapper && this.inited );
1294 },
1295
1296 /**
1297 * Checks if a widget is ready and has not been destroyed yet.
1298 *
1299 * See {@link #property-ready} for more details.
1300 *
1301 * @returns {Boolean}
1302 */
1303 isReady: function() {
1304 return this.isInited() && this.ready;
1305 },
1306
1307 /**
1308 * Focuses a widget by selecting it.
1309 */
1310 focus: function() {
1311 var sel = this.editor.getSelection();
1312
1313 // Fake the selection before focusing editor, to avoid unpreventable viewports scrolling
1314 // on Webkit/Blink/IE which is done because there's no selection or selection was somewhere else than widget.
1315 if ( sel ) {
1316 var isDirty = this.editor.checkDirty();
1317
1318 sel.fake( this.wrapper );
1319
1320 !isDirty && this.editor.resetDirty();
1321 }
1322
1323 // Always focus editor (not only when focusManger.hasFocus is false) (because of http://dev.ckeditor.com/ticket/10483).
1324 this.editor.focus();
1325 },
1326
1327 /**
1328 * Removes a class from the widget element. This method is used by
1329 * the {@link #removeStyle} method and should be overriden by widgets
1330 * which should handle classes differently (e.g. on other elements).
1331 *
1332 * **Note**: This method should not be used directly. Use the {@link #setData} method to
1333 * set the `classes` property. Read more in the {@link #setData} documentation.
1334 *
1335 * See also: {@link #hasClass}, {@link #addClass}.
1336 *
1337 * @since 4.4
1338 * @param {String} className The class to be removed.
1339 */
1340 removeClass: function( className ) {
1341 this.element.removeClass( className );
1342 this.wrapper.removeClass( Widget.WRAPPER_CLASS_PREFIX + className );
1343 },
1344
1345 /**
1346 * Removes the specified style from the widget. It is highly recommended to use the
1347 * {@link CKEDITOR.editor#removeStyle} or {@link CKEDITOR.style#remove} methods instead of
1348 * using this method directly, because unlike editor's and style's methods, this one
1349 * does not perform any checks.
1350 *
1351 * Read more about how applying/removing styles works in the {@link #applyStyle} method documentation.
1352 *
1353 * See also {@link #checkStyleActive}, {@link #applyStyle}, {@link #getClasses}.
1354 *
1355 * @since 4.4
1356 * @param {CKEDITOR.style} style The custom widget style to be removed.
1357 */
1358 removeStyle: function( style ) {
1359 applyRemoveStyle( this, style, 0 );
1360 },
1361
1362 /**
1363 * Sets widget value(s) in the {@link #property-data} object.
1364 * If the given value(s) modifies current ones, the {@link #event-data} event is fired.
1365 *
1366 * this.setData( 'align', 'left' );
1367 * this.data.align; // -> 'left'
1368 *
1369 * this.setData( { align: 'right', opened: false } );
1370 * this.data.align; // -> 'right'
1371 * this.data.opened; // -> false
1372 *
1373 * Set values are stored in {@link #element}'s attribute (`data-cke-widget-data`),
1374 * in a JSON string, therefore {@link #property-data} should contain
1375 * only serializable data.
1376 *
1377 * **Note:** A special data property, `classes`, exists. It contains an object with
1378 * classes which were returned by the {@link #getClasses} method during the widget initialization.
1379 * This property is then used by the {@link #applyStyle} and {@link #removeStyle} methods.
1380 * When it is changed (the reference to object must be changed!), the widget updates its classes by
1381 * using the {@link #addClass} and {@link #removeClass} methods.
1382 *
1383 * // Adding a new class.
1384 * var classes = CKEDITOR.tools.clone( widget.data.classes );
1385 * classes.newClass = 1;
1386 * widget.setData( 'classes', classes );
1387 *
1388 * // Removing a class.
1389 * var classes = CKEDITOR.tools.clone( widget.data.classes );
1390 * delete classes.newClass;
1391 * widget.setData( 'classes', classes );
1392 *
1393 * @param {String/Object} keyOrData
1394 * @param {Object} value
1395 * @chainable
1396 */
1397 setData: function( key, value ) {
1398 var data = this.data,
1399 modified = 0;
1400
1401 if ( typeof key == 'string' ) {
1402 if ( data[ key ] !== value ) {
1403 data[ key ] = value;
1404 modified = 1;
1405 }
1406 }
1407 else {
1408 var newData = key;
1409
1410 for ( key in newData ) {
1411 if ( data[ key ] !== newData[ key ] ) {
1412 modified = 1;
1413 data[ key ] = newData[ key ];
1414 }
1415 }
1416 }
1417
1418 // Block firing data event and overwriting data element before setupWidgetData is executed.
1419 if ( modified && this.dataReady ) {
1420 writeDataToElement( this );
1421 this.fire( 'data', data );
1422 }
1423
1424 return this;
1425 },
1426
1427 /**
1428 * Changes the widget's focus state. This method is executed automatically after
1429 * a widget was focused by the {@link #method-focus} method or the selection was moved
1430 * out of the widget.
1431 *
1432 * This is a low-level method which is not integrated with e.g. the undo manager.
1433 * Use the {@link #method-focus} method instead.
1434 *
1435 * @param {Boolean} selected Whether to select or deselect this widget.
1436 * @chainable
1437 */
1438 setFocused: function( focused ) {
1439 this.wrapper[ focused ? 'addClass' : 'removeClass' ]( 'cke_widget_focused' );
1440 this.fire( focused ? 'focus' : 'blur' );
1441 return this;
1442 },
1443
1444 /**
1445 * Changes the widget's select state. This method is executed automatically after
1446 * a widget was selected by the {@link #method-focus} method or the selection
1447 * was moved out of the widget.
1448 *
1449 * This is a low-level method which is not integrated with e.g. the undo manager.
1450 * Use the {@link #method-focus} method instead or simply change the selection.
1451 *
1452 * @param {Boolean} selected Whether to select or deselect this widget.
1453 * @chainable
1454 */
1455 setSelected: function( selected ) {
1456 this.wrapper[ selected ? 'addClass' : 'removeClass' ]( 'cke_widget_selected' );
1457 this.fire( selected ? 'select' : 'deselect' );
1458 return this;
1459 },
1460
1461 /**
1462 * Repositions drag handler according to the widget's element position. Should be called from events, like mouseover.
1463 */
1464 updateDragHandlerPosition: function() {
1465 var editor = this.editor,
1466 domElement = this.element.$,
1467 oldPos = this._.dragHandlerOffset,
1468 newPos = {
1469 x: domElement.offsetLeft,
1470 y: domElement.offsetTop - DRAG_HANDLER_SIZE
1471 };
1472
1473 if ( oldPos && newPos.x == oldPos.x && newPos.y == oldPos.y )
1474 return;
1475
1476 // We need to make sure that dirty state is not changed (http://dev.ckeditor.com/ticket/11487).
1477 var initialDirty = editor.checkDirty();
1478
1479 editor.fire( 'lockSnapshot' );
1480 this.dragHandlerContainer.setStyles( {
1481 top: newPos.y + 'px',
1482 left: newPos.x + 'px',
1483 display: 'block'
1484 } );
1485 editor.fire( 'unlockSnapshot' );
1486 !initialDirty && editor.resetDirty();
1487
1488 this._.dragHandlerOffset = newPos;
1489 }
1490 };
1491
1492 CKEDITOR.event.implementOn( Widget.prototype );
1493
1494 /**
1495 * Gets the {@link #isDomNestedEditable nested editable}
1496 * (returned as a {@link CKEDITOR.dom.element}, not as a {@link CKEDITOR.plugins.widget.nestedEditable})
1497 * closest to the `node` or the `node` if it is a nested editable itself.
1498 *
1499 * @since 4.5
1500 * @static
1501 * @param {CKEDITOR.dom.element} guard Stop ancestor search on this node (usually editor's editable).
1502 * @param {CKEDITOR.dom.node} node Start the search from this node.
1503 * @returns {CKEDITOR.dom.element/null} Element or `null` if not found.
1504 */
1505 Widget.getNestedEditable = function( guard, node ) {
1506 if ( !node || node.equals( guard ) )
1507 return null;
1508
1509 if ( Widget.isDomNestedEditable( node ) )
1510 return node;
1511
1512 return Widget.getNestedEditable( guard, node.getParent() );
1513 };
1514
1515 /**
1516 * Checks whether the `node` is a widget's drag handle element.
1517 *
1518 * @since 4.5
1519 * @static
1520 * @param {CKEDITOR.dom.node} node
1521 * @returns {Boolean}
1522 */
1523 Widget.isDomDragHandler = function( node ) {
1524 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-drag-handler' );
1525 };
1526
1527 /**
1528 * Checks whether the `node` is a container of the widget's drag handle element.
1529 *
1530 * @since 4.5
1531 * @static
1532 * @param {CKEDITOR.dom.node} node
1533 * @returns {Boolean}
1534 */
1535 Widget.isDomDragHandlerContainer = function( node ) {
1536 return node.type == CKEDITOR.NODE_ELEMENT && node.hasClass( 'cke_widget_drag_handler_container' );
1537 };
1538
1539 /**
1540 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#editables nested editable}.
1541 * Note that this function only checks whether it is the right element, not whether
1542 * the passed `node` is an instance of {@link CKEDITOR.plugins.widget.nestedEditable}.
1543 *
1544 * @since 4.5
1545 * @static
1546 * @param {CKEDITOR.dom.node} node
1547 * @returns {Boolean}
1548 */
1549 Widget.isDomNestedEditable = function( node ) {
1550 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-editable' );
1551 };
1552
1553 /**
1554 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1555 *
1556 * @since 4.5
1557 * @static
1558 * @param {CKEDITOR.dom.node} node
1559 * @returns {Boolean}
1560 */
1561 Widget.isDomWidgetElement = function( node ) {
1562 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-widget' );
1563 };
1564
1565 /**
1566 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1567 *
1568 * @since 4.5
1569 * @static
1570 * @param {CKEDITOR.dom.element} node
1571 * @returns {Boolean}
1572 */
1573 Widget.isDomWidgetWrapper = function( node ) {
1574 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-wrapper' );
1575 };
1576
1577 /**
1578 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1579 *
1580 * @since 4.5
1581 * @static
1582 * @param {CKEDITOR.htmlParser.node} node
1583 * @returns {Boolean}
1584 */
1585 Widget.isParserWidgetElement = function( node ) {
1586 return node.type == CKEDITOR.NODE_ELEMENT && !!node.attributes[ 'data-widget' ];
1587 };
1588
1589 /**
1590 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1591 *
1592 * @since 4.5
1593 * @static
1594 * @param {CKEDITOR.htmlParser.element} node
1595 * @returns {Boolean}
1596 */
1597 Widget.isParserWidgetWrapper = function( node ) {
1598 return node.type == CKEDITOR.NODE_ELEMENT && !!node.attributes[ 'data-cke-widget-wrapper' ];
1599 };
1600
1601 /**
1602 * Prefix added to wrapper classes. Each class added to the widget element by the {@link #addClass}
1603 * method will also be added to the wrapper prefixed with it.
1604 *
1605 * @since 4.6.0
1606 * @static
1607 * @readonly
1608 * @property {String} [='cke_widget_wrapper_']
1609 */
1610 Widget.WRAPPER_CLASS_PREFIX = 'cke_widget_wrapper_';
1611
1612 /**
1613 * An event fired when a widget is ready (fully initialized). This event is fired after:
1614 *
1615 * * {@link #init} is called,
1616 * * The first {@link #event-data} event is fired,
1617 * * A widget is attached to the document.
1618 *
1619 * Therefore, in case of widget creation with a command which opens a dialog window, this event
1620 * will be delayed after the dialog window is closed and the widget is finally inserted into the document.
1621 *
1622 * **Note**: If your widget does not use automatic dialog window binding (i.e. you open the dialog window manually)
1623 * or another situation in which the widget wrapper is not attached to document at the time when it is
1624 * initialized occurs, you need to take care of firing {@link #event-ready} yourself.
1625 *
1626 * See also {@link #property-ready} and {@link #property-inited} properties, and
1627 * {@link #isReady} and {@link #isInited} methods.
1628 *
1629 * @event ready
1630 */
1631
1632 /**
1633 * An event fired when a widget is about to be destroyed, but before it is
1634 * fully torn down.
1635 *
1636 * @event destroy
1637 */
1638
1639 /**
1640 * An event fired when a widget is focused.
1641 *
1642 * Widget can be focused by executing {@link #method-focus}.
1643 *
1644 * @event focus
1645 */
1646
1647 /**
1648 * An event fired when a widget is blurred.
1649 *
1650 * @event blur
1651 */
1652
1653 /**
1654 * An event fired when a widget is selected.
1655 *
1656 * @event select
1657 */
1658
1659 /**
1660 * An event fired when a widget is deselected.
1661 *
1662 * @event deselect
1663 */
1664
1665 /**
1666 * An event fired by the {@link #method-edit} method. It can be canceled
1667 * in order to stop the default action (opening a dialog window and/or
1668 * {@link CKEDITOR.plugins.widget.repository#finalizeCreation finalizing widget creation}).
1669 *
1670 * @event edit
1671 * @param data
1672 * @param {String} data.dialog Defaults to {@link CKEDITOR.plugins.widget.definition#dialog}
1673 * and can be changed or set by the listener.
1674 */
1675
1676 /**
1677 * An event fired when a dialog window for widget editing is opened.
1678 * This event can be canceled in order to handle the editing dialog in a custom manner.
1679 *
1680 * @event dialog
1681 * @param {CKEDITOR.dialog} data The opened dialog window instance.
1682 */
1683
1684 /**
1685 * An event fired when a key is pressed on a focused widget.
1686 * This event is forwarded from the {@link CKEDITOR.editor#key} event and
1687 * has the ability to block editor keystrokes if it is canceled.
1688 *
1689 * @event key
1690 * @param data
1691 * @param {Number} data.keyCode A number representing the key code (or combination).
1692 */
1693
1694 /**
1695 * An event fired when a widget is double clicked.
1696 *
1697 * **Note:** If a default editing action is executed on double click (i.e. a widget has a
1698 * {@link CKEDITOR.plugins.widget.definition#dialog dialog} defined and the {@link #event-doubleclick} event was not
1699 * canceled), this event will be automatically canceled, so a listener added with the default priority (10)
1700 * will not be executed. Use a listener with low priority (e.g. 5) to be sure that it will be executed.
1701 *
1702 * widget.on( 'doubleclick', function( evt ) {
1703 * console.log( 'widget#doubleclick' );
1704 * }, null, null, 5 );
1705 *
1706 * If your widget handles double click in a special way (so the default editing action is not executed),
1707 * make sure you cancel this event, because otherwise it will be propagated to {@link CKEDITOR.editor#doubleclick}
1708 * and another feature may step in (e.g. a Link dialog window may be opened if your widget was inside a link).
1709 *
1710 * @event doubleclick
1711 * @param data
1712 * @param {CKEDITOR.dom.element} data.element The double-clicked element.
1713 */
1714
1715 /**
1716 * An event fired when the context menu is opened for a widget.
1717 *
1718 * @event contextMenu
1719 * @param data The object containing context menu options to be added
1720 * for this widget. See {@link CKEDITOR.plugins.contextMenu#addListener}.
1721 */
1722
1723 /**
1724 * An event fired when the widget data changed. See the {@link #setData} method and the {@link #property-data} property.
1725 *
1726 * @event data
1727 */
1728
1729
1730
1731 /**
1732 * The wrapper class for editable elements inside widgets.
1733 *
1734 * Do not use directly. Use {@link CKEDITOR.plugins.widget.definition#editables} or
1735 * {@link CKEDITOR.plugins.widget#initEditable}.
1736 *
1737 * @class CKEDITOR.plugins.widget.nestedEditable
1738 * @extends CKEDITOR.dom.element
1739 * @constructor
1740 * @param {CKEDITOR.editor} editor
1741 * @param {CKEDITOR.dom.element} element
1742 * @param config
1743 * @param {CKEDITOR.filter} [config.filter]
1744 */
1745 function NestedEditable( editor, element, config ) {
1746 // Call the base constructor.
1747 CKEDITOR.dom.element.call( this, element.$ );
1748 this.editor = editor;
1749 this._ = {};
1750 var filter = this.filter = config.filter;
1751
1752 // If blockless editable - always use BR mode.
1753 if ( !CKEDITOR.dtd[ this.getName() ].p )
1754 this.enterMode = this.shiftEnterMode = CKEDITOR.ENTER_BR;
1755 else {
1756 this.enterMode = filter ? filter.getAllowedEnterMode( editor.enterMode ) : editor.enterMode;
1757 this.shiftEnterMode = filter ? filter.getAllowedEnterMode( editor.shiftEnterMode, true ) : editor.shiftEnterMode;
1758 }
1759 }
1760
1761 NestedEditable.prototype = CKEDITOR.tools.extend( CKEDITOR.tools.prototypedCopy( CKEDITOR.dom.element.prototype ), {
1762 /**
1763 * Sets the editable data. The data will be passed through the {@link CKEDITOR.editor#dataProcessor}
1764 * and the {@link CKEDITOR.editor#filter}. This ensures that the data was filtered and prepared to be
1765 * edited like the {@link CKEDITOR.editor#method-setData editor data}.
1766 *
1767 * Before content is changed, all nested widgets are destroyed. Afterwards, after new content is loaded,
1768 * all nested widgets are initialized.
1769 *
1770 * @param {String} data
1771 */
1772 setData: function( data ) {
1773 // For performance reasons don't call destroyAll when initializing a nested editable,
1774 // because there are no widgets inside.
1775 if ( !this._.initialSetData ) {
1776 // Destroy all nested widgets before setting data.
1777 this.editor.widgets.destroyAll( false, this );
1778 }
1779 this._.initialSetData = false;
1780
1781 data = this.editor.dataProcessor.toHtml( data, {
1782 context: this.getName(),
1783 filter: this.filter,
1784 enterMode: this.enterMode
1785 } );
1786 this.setHtml( data );
1787
1788 this.editor.widgets.initOnAll( this );
1789 },
1790
1791 /**
1792 * Gets the editable data. Like {@link #setData}, this method will process and filter the data.
1793 *
1794 * @returns {String}
1795 */
1796 getData: function() {
1797 return this.editor.dataProcessor.toDataFormat( this.getHtml(), {
1798 context: this.getName(),
1799 filter: this.filter,
1800 enterMode: this.enterMode
1801 } );
1802 }
1803 } );
1804
1805 /**
1806 * The editor instance.
1807 *
1808 * @readonly
1809 * @property {CKEDITOR.editor} editor
1810 */
1811
1812 /**
1813 * The filter instance if allowed content rules were defined.
1814 *
1815 * @readonly
1816 * @property {CKEDITOR.filter} filter
1817 */
1818
1819 /**
1820 * The enter mode active in this editable.
1821 * It is determined from editable's name (whether it is a blockless editable),
1822 * its allowed content rules (if defined) and the default editor's mode.
1823 *
1824 * @readonly
1825 * @property {Number} enterMode
1826 */
1827
1828 /**
1829 * The shift enter move active in this editable.
1830 *
1831 * @readonly
1832 * @property {Number} shiftEnterMode
1833 */
1834
1835
1836 //
1837 // REPOSITORY helpers -----------------------------------------------------
1838 //
1839
1840 function addWidgetButtons( editor ) {
1841 var widgets = editor.widgets.registered,
1842 widget,
1843 widgetName,
1844 widgetButton;
1845
1846 for ( widgetName in widgets ) {
1847 widget = widgets[ widgetName ];
1848
1849 // Create button if defined.
1850 widgetButton = widget.button;
1851 if ( widgetButton && editor.ui.addButton ) {
1852 editor.ui.addButton( CKEDITOR.tools.capitalize( widget.name, true ), {
1853 label: widgetButton,
1854 command: widget.name,
1855 toolbar: 'insert,10'
1856 } );
1857 }
1858 }
1859 }
1860
1861 // Create a command creating and editing widget.
1862 //
1863 // @param editor
1864 // @param {CKEDITOR.plugins.widget.definition} widgetDef
1865 function addWidgetCommand( editor, widgetDef ) {
1866 editor.addCommand( widgetDef.name, {
1867 exec: function( editor, commandData ) {
1868 var focused = editor.widgets.focused;
1869 // If a widget of the same type is focused, start editing.
1870 if ( focused && focused.name == widgetDef.name )
1871 focused.edit();
1872 // Otherwise...
1873 // ... use insert method is was defined.
1874 else if ( widgetDef.insert )
1875 widgetDef.insert();
1876 // ... or create a brand-new widget from template.
1877 else if ( widgetDef.template ) {
1878 var defaults = typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults,
1879 element = CKEDITOR.dom.element.createFromHtml( widgetDef.template.output( defaults ) ),
1880 instance,
1881 wrapper = editor.widgets.wrapElement( element, widgetDef.name ),
1882 temp = new CKEDITOR.dom.documentFragment( wrapper.getDocument() );
1883
1884 // Append wrapper to a temporary document. This will unify the environment
1885 // in which #data listeners work when creating and editing widget.
1886 temp.append( wrapper );
1887 instance = editor.widgets.initOn( element, widgetDef, commandData && commandData.startupData );
1888
1889 // Instance could be destroyed during initialization.
1890 // In this case finalize creation if some new widget
1891 // was left in temporary document fragment.
1892 if ( !instance ) {
1893 finalizeCreation();
1894 return;
1895 }
1896
1897 // Listen on edit to finalize widget insertion.
1898 //
1899 // * If dialog was set, then insert widget after dialog was successfully saved or destroy this
1900 // temporary instance.
1901 // * If dialog wasn't set and edit wasn't canceled, insert widget.
1902 var editListener = instance.once( 'edit', function( evt ) {
1903 if ( evt.data.dialog ) {
1904 instance.once( 'dialog', function( evt ) {
1905 var dialog = evt.data,
1906 okListener,
1907 cancelListener;
1908
1909 // Finalize creation AFTER (20) new data was set.
1910 okListener = dialog.once( 'ok', finalizeCreation, null, null, 20 );
1911
1912 cancelListener = dialog.once( 'cancel', function( evt ) {
1913 if ( !( evt.data && evt.data.hide === false ) ) {
1914 editor.widgets.destroy( instance, true );
1915 }
1916 } );
1917
1918 dialog.once( 'hide', function() {
1919 okListener.removeListener();
1920 cancelListener.removeListener();
1921 } );
1922 } );
1923 } else {
1924 // Dialog hasn't been set, so insert widget now.
1925 finalizeCreation();
1926 }
1927 }, null, null, 999 );
1928
1929 instance.edit();
1930
1931 // Remove listener in case someone canceled it before this
1932 // listener was executed.
1933 editListener.removeListener();
1934 }
1935
1936 function finalizeCreation() {
1937 editor.widgets.finalizeCreation( temp );
1938 }
1939 },
1940
1941 allowedContent: widgetDef.allowedContent,
1942 requiredContent: widgetDef.requiredContent,
1943 contentForms: widgetDef.contentForms,
1944 contentTransformations: widgetDef.contentTransformations
1945 } );
1946 }
1947
1948 function addWidgetProcessors( widgetsRepo, widgetDef ) {
1949 var upcast = widgetDef.upcast,
1950 upcasts,
1951 priority = widgetDef.upcastPriority || 10;
1952
1953 if ( !upcast )
1954 return;
1955
1956 // Multiple upcasts defined in string.
1957 if ( typeof upcast == 'string' ) {
1958 upcasts = upcast.split( ',' );
1959 while ( upcasts.length ) {
1960 addUpcast( widgetDef.upcasts[ upcasts.pop() ], widgetDef.name, priority );
1961 }
1962 }
1963 // Single rule which is automatically activated.
1964 else {
1965 addUpcast( upcast, widgetDef.name, priority );
1966 }
1967
1968 function addUpcast( upcast, name, priority ) {
1969 // Find index of the first higher (in terms of value) priority upcast.
1970 var index = CKEDITOR.tools.getIndex( widgetsRepo._.upcasts, function( element ) {
1971 return element[ 2 ] > priority;
1972 } );
1973 // Add at the end if it is the highest priority so far.
1974 if ( index < 0 ) {
1975 index = widgetsRepo._.upcasts.length;
1976 }
1977
1978 widgetsRepo._.upcasts.splice( index, 0, [ upcast, name, priority ] );
1979 }
1980 }
1981
1982 function blurWidget( widgetsRepo, widget ) {
1983 widgetsRepo.focused = null;
1984
1985 if ( widget.isInited() ) {
1986 var isDirty = widget.editor.checkDirty();
1987
1988 // Widget could be destroyed in the meantime - e.g. data could be set.
1989 widgetsRepo.fire( 'widgetBlurred', { widget: widget } );
1990 widget.setFocused( false );
1991
1992 !isDirty && widget.editor.resetDirty();
1993 }
1994 }
1995
1996 function checkWidgets( evt ) {
1997 var options = evt.data;
1998
1999 if ( this.editor.mode != 'wysiwyg' )
2000 return;
2001
2002 var editable = this.editor.editable(),
2003 instances = this.instances,
2004 newInstances, i, count, wrapper, notYetInitialized;
2005
2006 if ( !editable )
2007 return;
2008
2009 // Remove widgets which have no corresponding elements in DOM.
2010 for ( i in instances ) {
2011 // http://dev.ckeditor.com/ticket/13410 Remove widgets that are ready. This prevents from destroying widgets that are during loading process.
2012 if ( instances[ i ].isReady() && !editable.contains( instances[ i ].wrapper ) )
2013 this.destroy( instances[ i ], true );
2014 }
2015
2016 // Init on all (new) if initOnlyNew option was passed.
2017 if ( options && options.initOnlyNew )
2018 newInstances = this.initOnAll();
2019 else {
2020 var wrappers = editable.find( '.cke_widget_wrapper' );
2021 newInstances = [];
2022
2023 // Create widgets on existing wrappers if they do not exists.
2024 for ( i = 0, count = wrappers.count(); i < count; i++ ) {
2025 wrapper = wrappers.getItem( i );
2026 notYetInitialized = !this.getByElement( wrapper, true );
2027
2028 // Check if:
2029 // * there's no instance for this widget
2030 // * wrapper is not inside some temporary element like copybin (http://dev.ckeditor.com/ticket/11088)
2031 // * it was a nested widget's wrapper which has been detached from DOM,
2032 // when nested editable has been initialized (it overwrites its innerHTML
2033 // and initializes nested widgets).
2034 if ( notYetInitialized && !findParent( wrapper, isDomTemp ) && editable.contains( wrapper ) ) {
2035 // Add cke_widget_new class because otherwise
2036 // widget will not be created on such wrapper.
2037 wrapper.addClass( 'cke_widget_new' );
2038 newInstances.push( this.initOn( wrapper.getFirst( Widget.isDomWidgetElement ) ) );
2039 }
2040 }
2041 }
2042
2043 // If only single widget was initialized and focusInited was passed, focus it.
2044 if ( options && options.focusInited && newInstances.length == 1 )
2045 newInstances[ 0 ].focus();
2046 }
2047
2048 // Unwraps widget element and clean up element.
2049 //
2050 // This function is used to clean up pasted widgets.
2051 // It should have similar result to widget#destroy plus
2052 // some additional adjustments, specific for pasting.
2053 //
2054 // @param {CKEDITOR.htmlParser.element} el
2055 function cleanUpWidgetElement( el ) {
2056 var parent = el.parent;
2057 if ( parent.type == CKEDITOR.NODE_ELEMENT && parent.attributes[ 'data-cke-widget-wrapper' ] )
2058 parent.replaceWith( el );
2059 }
2060
2061 // Similar to cleanUpWidgetElement, but works on DOM and finds
2062 // widget elements by its own.
2063 //
2064 // Unlike cleanUpWidgetElement it will wrap element back.
2065 //
2066 // @param {CKEDITOR.dom.element} container
2067 function cleanUpAllWidgetElements( widgetsRepo, container ) {
2068 var wrappers = container.find( '.cke_widget_wrapper' ),
2069 wrapper, element,
2070 i = 0,
2071 l = wrappers.count();
2072
2073 for ( ; i < l; ++i ) {
2074 wrapper = wrappers.getItem( i );
2075 element = wrapper.getFirst( Widget.isDomWidgetElement );
2076 // If wrapper contains widget element - unwrap it and wrap again.
2077 if ( element.type == CKEDITOR.NODE_ELEMENT && element.data( 'widget' ) ) {
2078 element.replace( wrapper );
2079 widgetsRepo.wrapElement( element );
2080 } else {
2081 // Otherwise - something is wrong... clean this up.
2082 wrapper.remove();
2083 }
2084 }
2085 }
2086
2087 // Creates {@link CKEDITOR.filter} instance for given widget, editable and rules.
2088 //
2089 // Once filter for widget-editable pair is created it is cached, so the same instance
2090 // will be returned when method is executed again.
2091 //
2092 // @param {String} widgetName
2093 // @param {String} editableName
2094 // @param {CKEDITOR.plugins.widget.nestedEditableDefinition} editableDefinition The nested editable definition.
2095 // @returns {CKEDITOR.filter} Filter instance or `null` if rules are not defined.
2096 // @context CKEDITOR.plugins.widget.repository
2097 function createEditableFilter( widgetName, editableName, editableDefinition ) {
2098 if ( !editableDefinition.allowedContent )
2099 return null;
2100
2101 var editables = this._.filters[ widgetName ];
2102
2103 if ( !editables )
2104 this._.filters[ widgetName ] = editables = {};
2105
2106 var filter = editables[ editableName ];
2107
2108 if ( !filter )
2109 editables[ editableName ] = filter = new CKEDITOR.filter( editableDefinition.allowedContent );
2110
2111 return filter;
2112 }
2113
2114 // Creates an iterator function which when executed on all
2115 // elements in DOM tree will gather elements that should be wrapped
2116 // and initialized as widgets.
2117 function createUpcastIterator( widgetsRepo ) {
2118 var toBeWrapped = [],
2119 upcasts = widgetsRepo._.upcasts,
2120 upcastCallbacks = widgetsRepo._.upcastCallbacks;
2121
2122 return {
2123 toBeWrapped: toBeWrapped,
2124
2125 iterator: function( element ) {
2126 var upcast, upcasted,
2127 data,
2128 i,
2129 upcastsLength,
2130 upcastCallbacksLength;
2131
2132 // Wrapper found - find widget element, add it to be
2133 // cleaned up (unwrapped) and wrapped and stop iterating in this branch.
2134 if ( 'data-cke-widget-wrapper' in element.attributes ) {
2135 element = element.getFirst( Widget.isParserWidgetElement );
2136
2137 if ( element )
2138 toBeWrapped.push( [ element ] );
2139
2140 // Do not iterate over descendants.
2141 return false;
2142 }
2143 // Widget element found - add it to be cleaned up (just in case)
2144 // and wrapped and stop iterating in this branch.
2145 else if ( 'data-widget' in element.attributes ) {
2146 toBeWrapped.push( [ element ] );
2147
2148 // Do not iterate over descendants.
2149 return false;
2150 }
2151 else if ( ( upcastsLength = upcasts.length ) ) {
2152 // Ignore elements with data-cke-widget-upcasted to avoid multiple upcasts (http://dev.ckeditor.com/ticket/11533).
2153 // Do not iterate over descendants.
2154 if ( element.attributes[ 'data-cke-widget-upcasted' ] )
2155 return false;
2156
2157 // Check element with upcast callbacks first.
2158 // If any of them return false abort upcasting.
2159 for ( i = 0, upcastCallbacksLength = upcastCallbacks.length; i < upcastCallbacksLength; ++i ) {
2160 if ( upcastCallbacks[ i ]( element ) === false )
2161 return;
2162 // Return nothing in order to continue iterating over ascendants.
2163 // See http://dev.ckeditor.com/ticket/11186#comment:6
2164 }
2165
2166 for ( i = 0; i < upcastsLength; ++i ) {
2167 upcast = upcasts[ i ];
2168 data = {};
2169
2170 if ( ( upcasted = upcast[ 0 ]( element, data ) ) ) {
2171 // If upcast function returned element, upcast this one.
2172 // It can be e.g. a new element wrapping the original one.
2173 if ( upcasted instanceof CKEDITOR.htmlParser.element )
2174 element = upcasted;
2175
2176 // Set initial data attr with data from upcast method.
2177 element.attributes[ 'data-cke-widget-data' ] = encodeURIComponent( JSON.stringify( data ) );
2178 element.attributes[ 'data-cke-widget-upcasted' ] = 1;
2179
2180 toBeWrapped.push( [ element, upcast[ 1 ] ] );
2181
2182 // Do not iterate over descendants.
2183 return false;
2184 }
2185 }
2186 }
2187 }
2188 };
2189 }
2190
2191 // Finds a first parent that matches query.
2192 //
2193 // @param {CKEDITOR.dom.element} element
2194 // @param {Function} query
2195 function findParent( element, query ) {
2196 var parent = element;
2197
2198 while ( ( parent = parent.getParent() ) ) {
2199 if ( query( parent ) )
2200 return true;
2201 }
2202 return false;
2203 }
2204
2205 function getWrapperAttributes( inlineWidget, name ) {
2206 return {
2207 // tabindex="-1" means that it can receive focus by code.
2208 tabindex: -1,
2209 contenteditable: 'false',
2210 'data-cke-widget-wrapper': 1,
2211 'data-cke-filter': 'off',
2212 // Class cke_widget_new marks widgets which haven't been initialized yet.
2213 'class': 'cke_widget_wrapper cke_widget_new cke_widget_' +
2214 ( inlineWidget ? 'inline' : 'block' ) +
2215 ( name ? ' cke_widget_' + name : '' )
2216 };
2217 }
2218
2219 // Inserts element at given index.
2220 // It will check DTD and split ancestor elements up to the first
2221 // that can contain this element.
2222 //
2223 // @param {CKEDITOR.htmlParser.element} parent
2224 // @param {Number} index
2225 // @param {CKEDITOR.htmlParser.element} element
2226 function insertElement( parent, index, element ) {
2227 // Do not split doc fragment...
2228 if ( parent.type == CKEDITOR.NODE_ELEMENT ) {
2229 var parentAllows = CKEDITOR.dtd[ parent.name ];
2230 // Parent element is known (included in DTD) and cannot contain
2231 // this element.
2232 if ( parentAllows && !parentAllows[ element.name ] ) {
2233 var parent2 = parent.split( index ),
2234 parentParent = parent.parent;
2235
2236 // Element will now be inserted at right parent's index.
2237 index = parent2.getIndex();
2238
2239 // If left part of split is empty - remove it.
2240 if ( !parent.children.length ) {
2241 index -= 1;
2242 parent.remove();
2243 }
2244
2245 // If right part of split is empty - remove it.
2246 if ( !parent2.children.length )
2247 parent2.remove();
2248
2249 // Try inserting as grandpas' children.
2250 return insertElement( parentParent, index, element );
2251 }
2252 }
2253
2254 // Finally we can add this element.
2255 parent.add( element, index );
2256 }
2257
2258 // Checks whether for the given widget definition and element widget should be created in inline or block mode.
2259 //
2260 // See also: {@link CKEDITOR.plugins.widget.definition#inline} and {@link CKEDITOR.plugins.widget#element}.
2261 //
2262 // @param {CKEDITOR.plugins.widget.definition} widgetDef The widget definition.
2263 // @param {String} elementName The name of the widget element.
2264 // @returns {Boolean}
2265 function isWidgetInline( widgetDef, elementName ) {
2266 return typeof widgetDef.inline == 'boolean' ? widgetDef.inline : !!CKEDITOR.dtd.$inline[ elementName ];
2267 }
2268
2269 // @param {CKEDITOR.dom.element}
2270 // @returns {Boolean}
2271 function isDomTemp( element ) {
2272 return element.hasAttribute( 'data-cke-temp' );
2273 }
2274
2275 function onEditableKey( widget, keyCode ) {
2276 var focusedEditable = widget.focusedEditable,
2277 range;
2278
2279 // CTRL+A.
2280 if ( keyCode == CKEDITOR.CTRL + 65 ) {
2281 var bogus = focusedEditable.getBogus();
2282
2283 range = widget.editor.createRange();
2284 range.selectNodeContents( focusedEditable );
2285 // Exclude bogus if exists.
2286 if ( bogus )
2287 range.setEndAt( bogus, CKEDITOR.POSITION_BEFORE_START );
2288
2289 range.select();
2290 // Cancel event - block default.
2291 return false;
2292 }
2293 // DEL or BACKSPACE.
2294 else if ( keyCode == 8 || keyCode == 46 ) {
2295 var ranges = widget.editor.getSelection().getRanges();
2296
2297 range = ranges[ 0 ];
2298
2299 // Block del or backspace if at editable's boundary.
2300 return !( ranges.length == 1 && range.collapsed &&
2301 range.checkBoundaryOfElement( focusedEditable, CKEDITOR[ keyCode == 8 ? 'START' : 'END' ] ) );
2302 }
2303 }
2304
2305 function setFocusedEditable( widgetsRepo, widget, editableElement, offline ) {
2306 var editor = widgetsRepo.editor;
2307
2308 editor.fire( 'lockSnapshot' );
2309
2310 if ( editableElement ) {
2311 var editableName = editableElement.data( 'cke-widget-editable' ),
2312 editableInstance = widget.editables[ editableName ];
2313
2314 widgetsRepo.widgetHoldingFocusedEditable = widget;
2315 widget.focusedEditable = editableInstance;
2316 editableElement.addClass( 'cke_widget_editable_focused' );
2317
2318 if ( editableInstance.filter )
2319 editor.setActiveFilter( editableInstance.filter );
2320 editor.setActiveEnterMode( editableInstance.enterMode, editableInstance.shiftEnterMode );
2321 } else {
2322 if ( !offline )
2323 widget.focusedEditable.removeClass( 'cke_widget_editable_focused' );
2324
2325 widget.focusedEditable = null;
2326 widgetsRepo.widgetHoldingFocusedEditable = null;
2327 editor.setActiveFilter( null );
2328 editor.setActiveEnterMode( null, null );
2329 }
2330
2331 editor.fire( 'unlockSnapshot' );
2332 }
2333
2334 function setupContextMenu( editor ) {
2335 if ( !editor.contextMenu )
2336 return;
2337
2338 editor.contextMenu.addListener( function( element ) {
2339 var widget = editor.widgets.getByElement( element, true );
2340
2341 if ( widget )
2342 return widget.fire( 'contextMenu', {} );
2343 } );
2344 }
2345
2346 // And now we've got two problems - original problem and RegExp.
2347 // Some softeners:
2348 // * FF tends to copy all blocks up to the copybin container.
2349 // * IE tends to copy only the copybin, without its container.
2350 // * We use spans on IE and blockless editors, but divs in other cases.
2351 var pasteReplaceRegex = new RegExp(
2352 '^' +
2353 '(?:<(?:div|span)(?: data-cke-temp="1")?(?: id="cke_copybin")?(?: data-cke-temp="1")?>)?' +
2354 '(?:<(?:div|span)(?: style="[^"]+")?>)?' +
2355 '<span [^>]*data-cke-copybin-start="1"[^>]*>.?</span>([\\s\\S]+)<span [^>]*data-cke-copybin-end="1"[^>]*>.?</span>' +
2356 '(?:</(?:div|span)>)?' +
2357 '(?:</(?:div|span)>)?' +
2358 '$',
2359 // IE8 prefers uppercase when browsers stick to lowercase HTML (http://dev.ckeditor.com/ticket/13460).
2360 'i'
2361 );
2362
2363 function pasteReplaceFn( match, wrapperHtml ) {
2364 // Avoid polluting pasted data with any whitspaces,
2365 // what's going to break check whether only one widget was pasted.
2366 return CKEDITOR.tools.trim( wrapperHtml );
2367 }
2368
2369 function setupDragAndDrop( widgetsRepo ) {
2370 var editor = widgetsRepo.editor,
2371 lineutils = CKEDITOR.plugins.lineutils;
2372
2373 // These listeners handle inline and block widgets drag and drop.
2374 // The only thing we need to do to make block widgets custom drag and drop functionality
2375 // is to fire those events with the right properties (like the target which must be the drag handle).
2376 editor.on( 'dragstart', function( evt ) {
2377 var target = evt.data.target;
2378
2379 if ( Widget.isDomDragHandler( target ) ) {
2380 var widget = widgetsRepo.getByElement( target );
2381
2382 evt.data.dataTransfer.setData( 'cke/widget-id', widget.id );
2383
2384 // IE needs focus.
2385 editor.focus();
2386
2387 // and widget need to be focused on drag start (http://dev.ckeditor.com/ticket/12172#comment:10).
2388 widget.focus();
2389 }
2390 } );
2391
2392 editor.on( 'drop', function( evt ) {
2393 var dataTransfer = evt.data.dataTransfer,
2394 id = dataTransfer.getData( 'cke/widget-id' ),
2395 transferType = dataTransfer.getTransferType( editor ),
2396 dragRange = editor.createRange(),
2397 sourceWidget;
2398
2399 // Disable cross-editor drag & drop for widgets - http://dev.ckeditor.com/ticket/13599.
2400 if ( id !== '' && transferType === CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) {
2401 evt.cancel();
2402 return;
2403 }
2404
2405 if ( id === '' || transferType != CKEDITOR.DATA_TRANSFER_INTERNAL ) {
2406 return;
2407 }
2408
2409 sourceWidget = widgetsRepo.instances[ id ];
2410 if ( !sourceWidget ) {
2411 return;
2412 }
2413
2414 dragRange.setStartBefore( sourceWidget.wrapper );
2415 dragRange.setEndAfter( sourceWidget.wrapper );
2416 evt.data.dragRange = dragRange;
2417
2418 // [IE8-9] Reset state of the clipboard#fixSplitNodesAfterDrop fix because by setting evt.data.dragRange
2419 // (see above) after drop happened we do not need it. That fix is needed only if dragRange was created
2420 // before drop (before text node was split).
2421 delete CKEDITOR.plugins.clipboard.dragStartContainerChildCount;
2422 delete CKEDITOR.plugins.clipboard.dragEndContainerChildCount;
2423
2424 evt.data.dataTransfer.setData( 'text/html', editor.editable().getHtmlFromRange( dragRange ).getHtml() );
2425 editor.widgets.destroy( sourceWidget, true );
2426 } );
2427
2428 editor.on( 'contentDom', function() {
2429 var editable = editor.editable();
2430
2431 // Register Lineutils's utilities as properties of repo.
2432 CKEDITOR.tools.extend( widgetsRepo, {
2433 finder: new lineutils.finder( editor, {
2434 lookups: {
2435 // Element is block but not list item and not in nested editable.
2436 'default': function( el ) {
2437 if ( el.is( CKEDITOR.dtd.$listItem ) )
2438 return;
2439
2440 if ( !el.is( CKEDITOR.dtd.$block ) )
2441 return;
2442
2443 // Allow drop line inside, but never before or after nested editable (http://dev.ckeditor.com/ticket/12006).
2444 if ( Widget.isDomNestedEditable( el ) )
2445 return;
2446
2447 // Do not allow droping inside the widget being dragged (http://dev.ckeditor.com/ticket/13397).
2448 if ( widgetsRepo._.draggedWidget.wrapper.contains( el ) ) {
2449 return;
2450 }
2451
2452 // If element is nested editable, make sure widget can be dropped there (http://dev.ckeditor.com/ticket/12006).
2453 var nestedEditable = Widget.getNestedEditable( editable, el );
2454 if ( nestedEditable ) {
2455 var draggedWidget = widgetsRepo._.draggedWidget;
2456
2457 // Don't let the widget to be dropped into its own nested editable.
2458 if ( widgetsRepo.getByElement( nestedEditable ) == draggedWidget )
2459 return;
2460
2461 var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ],
2462 draggedRequiredContent = draggedWidget.requiredContent;
2463
2464 // There will be no relation if the filter of nested editable does not allow
2465 // requiredContent of dragged widget.
2466 if ( filter && draggedRequiredContent && !filter.check( draggedRequiredContent ) )
2467 return;
2468 }
2469
2470 return CKEDITOR.LINEUTILS_BEFORE | CKEDITOR.LINEUTILS_AFTER;
2471 }
2472 }
2473 } ),
2474 locator: new lineutils.locator( editor ),
2475 liner: new lineutils.liner( editor, {
2476 lineStyle: {
2477 cursor: 'move !important',
2478 'border-top-color': '#666'
2479 },
2480 tipLeftStyle: {
2481 'border-left-color': '#666'
2482 },
2483 tipRightStyle: {
2484 'border-right-color': '#666'
2485 }
2486 } )
2487 }, true );
2488 } );
2489 }
2490
2491 // Setup mouse observer which will trigger:
2492 // * widget focus on widget click,
2493 // * widget#doubleclick forwarded from editor#doubleclick.
2494 function setupMouseObserver( widgetsRepo ) {
2495 var editor = widgetsRepo.editor;
2496
2497 editor.on( 'contentDom', function() {
2498 var editable = editor.editable(),
2499 evtRoot = editable.isInline() ? editable : editor.document,
2500 widget,
2501 mouseDownOnDragHandler;
2502
2503 editable.attachListener( evtRoot, 'mousedown', function( evt ) {
2504 var target = evt.data.getTarget();
2505
2506 // http://dev.ckeditor.com/ticket/10887 Clicking scrollbar in IE8 will invoke event with empty target object.
2507 if ( !target.type )
2508 return false;
2509
2510 widget = widgetsRepo.getByElement( target );
2511 mouseDownOnDragHandler = 0; // Reset.
2512
2513 // Widget was clicked, but not editable nested in it.
2514 if ( widget ) {
2515 // Ignore mousedown on drag and drop handler if the widget is inline.
2516 // Block widgets are handled by Lineutils.
2517 if ( widget.inline && target.type == CKEDITOR.NODE_ELEMENT && target.hasAttribute( 'data-cke-widget-drag-handler' ) ) {
2518 mouseDownOnDragHandler = 1;
2519
2520 // When drag handler is pressed we have to clear current selection if it wasn't already on this widget.
2521 // Otherwise, the selection may be in a fillingChar, which prevents dragging a widget. (http://dev.ckeditor.com/ticket/13284, see comment 8 and 9.)
2522 if ( widgetsRepo.focused != widget )
2523 editor.getSelection().removeAllRanges();
2524
2525 return;
2526 }
2527
2528 if ( !Widget.getNestedEditable( widget.wrapper, target ) ) {
2529 evt.data.preventDefault();
2530 if ( !CKEDITOR.env.ie )
2531 widget.focus();
2532 } else {
2533 // Reset widget so mouseup listener is not confused.
2534 widget = null;
2535 }
2536 }
2537 } );
2538
2539 // Focus widget on mouseup if mousedown was fired on drag handler.
2540 // Note: mouseup won't be fired at all if widget was dragged and dropped, so
2541 // this code will be executed only when drag handler was clicked.
2542 editable.attachListener( evtRoot, 'mouseup', function() {
2543 // Check if widget is not destroyed (if widget is destroyed the wrapper will be null).
2544 if ( mouseDownOnDragHandler && widget && widget.wrapper ) {
2545 mouseDownOnDragHandler = 0;
2546 widget.focus();
2547 }
2548 } );
2549
2550 // On IE it is not enough to block mousedown. If widget wrapper (element with
2551 // contenteditable=false attribute) is clicked directly (it is a target),
2552 // then after mouseup/click IE will select that element.
2553 // It is not possible to prevent that default action,
2554 // so we force fake selection after everything happened.
2555 if ( CKEDITOR.env.ie ) {
2556 editable.attachListener( evtRoot, 'mouseup', function() {
2557 setTimeout( function() {
2558 // Check if widget is not destroyed (if widget is destroyed the wrapper will be null) and
2559 // in editable contains widget (it could be dragged and removed).
2560 if ( widget && widget.wrapper && editable.contains( widget.wrapper ) ) {
2561 widget.focus();
2562 widget = null;
2563 }
2564 } );
2565 } );
2566 }
2567 } );
2568
2569 editor.on( 'doubleclick', function( evt ) {
2570 var widget = widgetsRepo.getByElement( evt.data.element );
2571
2572 // Not in widget or in nested editable.
2573 if ( !widget || Widget.getNestedEditable( widget.wrapper, evt.data.element ) )
2574 return;
2575
2576 return widget.fire( 'doubleclick', { element: evt.data.element } );
2577 }, null, null, 1 );
2578 }
2579
2580 // Setup editor#key observer which will forward it
2581 // to focused widget.
2582 function setupKeyboardObserver( widgetsRepo ) {
2583 var editor = widgetsRepo.editor;
2584
2585 editor.on( 'key', function( evt ) {
2586 var focused = widgetsRepo.focused,
2587 widgetHoldingFocusedEditable = widgetsRepo.widgetHoldingFocusedEditable,
2588 ret;
2589
2590 if ( focused )
2591 ret = focused.fire( 'key', { keyCode: evt.data.keyCode } );
2592 else if ( widgetHoldingFocusedEditable )
2593 ret = onEditableKey( widgetHoldingFocusedEditable, evt.data.keyCode );
2594
2595 return ret;
2596 }, null, null, 1 );
2597 }
2598
2599 // Setup copybin on native copy and cut events in order to handle copy and cut commands
2600 // if user accepted security alert on IEs.
2601 // Note: when copying or cutting using keystroke, copySingleWidget will be first executed
2602 // by the keydown listener. Conflict between two calls will be resolved by copy_bin existence check.
2603 function setupNativeCutAndCopy( widgetsRepo ) {
2604 var editor = widgetsRepo.editor;
2605
2606 editor.on( 'contentDom', function() {
2607 var editable = editor.editable();
2608
2609 editable.attachListener( editable, 'copy', eventListener );
2610 editable.attachListener( editable, 'cut', eventListener );
2611 } );
2612
2613 function eventListener( evt ) {
2614 if ( widgetsRepo.focused )
2615 copySingleWidget( widgetsRepo.focused, evt.name == 'cut' );
2616 }
2617 }
2618
2619 // Setup selection observer which will trigger:
2620 // * widget select & focus on selection change,
2621 // * nested editable focus (related properites and classes) on selection change,
2622 // * deselecting and blurring all widgets on data,
2623 // * blurring widget on editor blur.
2624 function setupSelectionObserver( widgetsRepo ) {
2625 var editor = widgetsRepo.editor;
2626
2627 editor.on( 'selectionCheck', function() {
2628 widgetsRepo.fire( 'checkSelection' );
2629 } );
2630
2631 widgetsRepo.on( 'checkSelection', widgetsRepo.checkSelection, widgetsRepo );
2632
2633 editor.on( 'selectionChange', function( evt ) {
2634 var nestedEditable = Widget.getNestedEditable( editor.editable(), evt.data.selection.getStartElement() ),
2635 newWidget = nestedEditable && widgetsRepo.getByElement( nestedEditable ),
2636 oldWidget = widgetsRepo.widgetHoldingFocusedEditable;
2637
2638 if ( oldWidget ) {
2639 if ( oldWidget !== newWidget || !oldWidget.focusedEditable.equals( nestedEditable ) ) {
2640 setFocusedEditable( widgetsRepo, oldWidget, null );
2641
2642 if ( newWidget && nestedEditable )
2643 setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
2644 }
2645 }
2646 // It may happen that there's no widget even if editable was found -
2647 // e.g. if selection was automatically set in editable although widget wasn't initialized yet.
2648 else if ( newWidget && nestedEditable ) {
2649 setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
2650 }
2651 } );
2652
2653 // Invalidate old widgets early - immediately on dataReady.
2654 editor.on( 'dataReady', function() {
2655 // Deselect and blur all widgets.
2656 stateUpdater( widgetsRepo ).commit();
2657 } );
2658
2659 editor.on( 'blur', function() {
2660 var widget;
2661
2662 if ( ( widget = widgetsRepo.focused ) )
2663 blurWidget( widgetsRepo, widget );
2664
2665 if ( ( widget = widgetsRepo.widgetHoldingFocusedEditable ) )
2666 setFocusedEditable( widgetsRepo, widget, null );
2667 } );
2668 }
2669
2670 // Set up actions like:
2671 // * processing in toHtml/toDataFormat,
2672 // * pasting handling,
2673 // * insertion handling,
2674 // * editable reload handling (setData, mode switch, undo/redo),
2675 // * DOM invalidation handling,
2676 // * widgets checks.
2677 function setupWidgetsLifecycle( widgetsRepo ) {
2678 setupWidgetsLifecycleStart( widgetsRepo );
2679 setupWidgetsLifecycleEnd( widgetsRepo );
2680
2681 widgetsRepo.on( 'checkWidgets', checkWidgets );
2682 widgetsRepo.editor.on( 'contentDomInvalidated', widgetsRepo.checkWidgets, widgetsRepo );
2683 }
2684
2685 function setupWidgetsLifecycleEnd( widgetsRepo ) {
2686 var editor = widgetsRepo.editor,
2687 downcastingSessions = {};
2688
2689 // Listen before htmlDP#htmlFilter is applied to cache all widgets, because we'll
2690 // loose data-cke-* attributes.
2691 editor.on( 'toDataFormat', function( evt ) {
2692 // To avoid conflicts between htmlDP#toDF calls done at the same time
2693 // (e.g. nestedEditable#getData called during downcasting some widget)
2694 // mark every toDataFormat event chain with the downcasting session id.
2695 var id = CKEDITOR.tools.getNextNumber(),
2696 toBeDowncasted = [];
2697 evt.data.downcastingSessionId = id;
2698 downcastingSessions[ id ] = toBeDowncasted;
2699
2700 evt.data.dataValue.forEach( function( element ) {
2701 var attrs = element.attributes,
2702 widget, widgetElement;
2703
2704 // Wrapper.
2705 // Perform first part of downcasting (cleanup) and cache widgets,
2706 // because after applying DP's filter all data-cke-* attributes will be gone.
2707 if ( 'data-cke-widget-id' in attrs ) {
2708 widget = widgetsRepo.instances[ attrs[ 'data-cke-widget-id' ] ];
2709 if ( widget ) {
2710 widgetElement = element.getFirst( Widget.isParserWidgetElement );
2711 toBeDowncasted.push( {
2712 wrapper: element,
2713 element: widgetElement,
2714 widget: widget,
2715 editables: {}
2716 } );
2717
2718 // If widget did not have data-cke-widget attribute before upcasting remove it.
2719 if ( widgetElement.attributes[ 'data-cke-widget-keep-attr' ] != '1' )
2720 delete widgetElement.attributes[ 'data-widget' ];
2721 }
2722 }
2723 // Nested editable.
2724 else if ( 'data-cke-widget-editable' in attrs ) {
2725 // Save the reference to this nested editable in the closest widget to be downcasted.
2726 // Nested editables are downcasted in the successive toDataFormat to create an opportunity
2727 // for dataFilter's "excludeNestedEditable" option to do its job (that option relies on
2728 // contenteditable="true" attribute) (http://dev.ckeditor.com/ticket/11372).
2729 toBeDowncasted[ toBeDowncasted.length - 1 ].editables[ attrs[ 'data-cke-widget-editable' ] ] = element;
2730
2731 // Don't check children - there won't be next wrapper or nested editable which we
2732 // should process in this session.
2733 return false;
2734 }
2735 }, CKEDITOR.NODE_ELEMENT, true );
2736 }, null, null, 8 );
2737
2738 // Listen after dataProcessor.htmlFilter and ACF were applied
2739 // so wrappers securing widgets' contents are removed after all filtering was done.
2740 editor.on( 'toDataFormat', function( evt ) {
2741 // Ignore some unmarked sessions.
2742 if ( !evt.data.downcastingSessionId )
2743 return;
2744
2745 var toBeDowncasted = downcastingSessions[ evt.data.downcastingSessionId ],
2746 toBe, widget, widgetElement, retElement, editableElement, e;
2747
2748 while ( ( toBe = toBeDowncasted.shift() ) ) {
2749 widget = toBe.widget;
2750 widgetElement = toBe.element;
2751 retElement = widget._.downcastFn && widget._.downcastFn.call( widget, widgetElement );
2752
2753 // Replace nested editables' content with their output data.
2754 for ( e in toBe.editables ) {
2755 editableElement = toBe.editables[ e ];
2756
2757 delete editableElement.attributes.contenteditable;
2758 editableElement.setHtml( widget.editables[ e ].getData() );
2759 }
2760
2761 // Returned element always defaults to widgetElement.
2762 if ( !retElement )
2763 retElement = widgetElement;
2764
2765 toBe.wrapper.replaceWith( retElement );
2766 }
2767 }, null, null, 13 );
2768
2769
2770 editor.on( 'contentDomUnload', function() {
2771 widgetsRepo.destroyAll( true );
2772 } );
2773 }
2774
2775 function setupWidgetsLifecycleStart( widgetsRepo ) {
2776 var editor = widgetsRepo.editor,
2777 processedWidgetOnly,
2778 snapshotLoaded;
2779
2780 // Listen after ACF (so data are filtered),
2781 // but before dataProcessor.dataFilter was applied (so we can secure widgets' internals).
2782 editor.on( 'toHtml', function( evt ) {
2783 var upcastIterator = createUpcastIterator( widgetsRepo ),
2784 toBeWrapped;
2785
2786 evt.data.dataValue.forEach( upcastIterator.iterator, CKEDITOR.NODE_ELEMENT, true );
2787
2788 // Clean up and wrap all queued elements.
2789 while ( ( toBeWrapped = upcastIterator.toBeWrapped.pop() ) ) {
2790 cleanUpWidgetElement( toBeWrapped[ 0 ] );
2791 widgetsRepo.wrapElement( toBeWrapped[ 0 ], toBeWrapped[ 1 ] );
2792 }
2793
2794 // Used to determine whether only widget was pasted.
2795 if ( evt.data.protectedWhitespaces ) {
2796 // Whitespaces are protected by wrapping content with spans. Take the middle node only.
2797 processedWidgetOnly = evt.data.dataValue.children.length == 3 &&
2798 Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 1 ] );
2799 } else {
2800 processedWidgetOnly = evt.data.dataValue.children.length == 1 &&
2801 Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 0 ] );
2802 }
2803 }, null, null, 8 );
2804
2805 editor.on( 'dataReady', function() {
2806 // Clean up all widgets loaded from snapshot.
2807 if ( snapshotLoaded )
2808 cleanUpAllWidgetElements( widgetsRepo, editor.editable() );
2809 snapshotLoaded = 0;
2810
2811 // Some widgets were destroyed on contentDomUnload,
2812 // some on loadSnapshot, but that does not include
2813 // e.g. setHtml on inline editor or widgets removed just
2814 // before setting data.
2815 widgetsRepo.destroyAll( true );
2816 widgetsRepo.initOnAll();
2817 } );
2818
2819 // Set flag so dataReady will know that additional
2820 // cleanup is needed, because snapshot containing widgets was loaded.
2821 editor.on( 'loadSnapshot', function( evt ) {
2822 // Primitive but sufficient check which will prevent from executing
2823 // heavier cleanUpAllWidgetElements if not needed.
2824 if ( ( /data-cke-widget/ ).test( evt.data ) )
2825 snapshotLoaded = 1;
2826
2827 widgetsRepo.destroyAll( true );
2828 }, null, null, 9 );
2829
2830 // Handle pasted single widget.
2831 editor.on( 'paste', function( evt ) {
2832 var data = evt.data;
2833
2834 data.dataValue = data.dataValue.replace( pasteReplaceRegex, pasteReplaceFn );
2835
2836 // If drag'n'drop kind of paste into nested editable (data.range), selection is set AFTER
2837 // data is pasted, which means editor has no chance to change activeFilter's context.
2838 // As a result, pasted data is filtered with default editor's filter instead of NE's and
2839 // funny things get inserted. Changing the filter by analysis of the paste range below (http://dev.ckeditor.com/ticket/13186).
2840 if ( data.range ) {
2841 // Check if pasting into nested editable.
2842 var nestedEditable = Widget.getNestedEditable( editor.editable(), data.range.startContainer );
2843
2844 if ( nestedEditable ) {
2845 // Retrieve the filter from NE's data and set it active before editor.insertHtml is done
2846 // in clipboard plugin.
2847 var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ];
2848
2849 if ( filter ) {
2850 editor.setActiveFilter( filter );
2851 }
2852 }
2853 }
2854 } );
2855
2856 // Listen with high priority to check widgets after data was inserted.
2857 editor.on( 'afterInsertHtml', function( evt ) {
2858 if ( evt.data.intoRange ) {
2859 widgetsRepo.checkWidgets( { initOnlyNew: true } );
2860 } else {
2861 editor.fire( 'lockSnapshot' );
2862 // Init only new for performance reason.
2863 // Focus inited if only widget was processed.
2864 widgetsRepo.checkWidgets( { initOnlyNew: true, focusInited: processedWidgetOnly } );
2865
2866 editor.fire( 'unlockSnapshot' );
2867 }
2868 } );
2869 }
2870
2871 // Helper for coordinating which widgets should be
2872 // selected/deselected and which one should be focused/blurred.
2873 function stateUpdater( widgetsRepo ) {
2874 var currentlySelected = widgetsRepo.selected,
2875 toBeSelected = [],
2876 toBeDeselected = currentlySelected.slice( 0 ),
2877 focused = null;
2878
2879 return {
2880 select: function( widget ) {
2881 if ( CKEDITOR.tools.indexOf( currentlySelected, widget ) < 0 )
2882 toBeSelected.push( widget );
2883
2884 var index = CKEDITOR.tools.indexOf( toBeDeselected, widget );
2885 if ( index >= 0 )
2886 toBeDeselected.splice( index, 1 );
2887
2888 return this;
2889 },
2890
2891 focus: function( widget ) {
2892 focused = widget;
2893 return this;
2894 },
2895
2896 commit: function() {
2897 var focusedChanged = widgetsRepo.focused !== focused,
2898 widget, isDirty;
2899
2900 widgetsRepo.editor.fire( 'lockSnapshot' );
2901
2902 if ( focusedChanged && ( widget = widgetsRepo.focused ) )
2903 blurWidget( widgetsRepo, widget );
2904
2905 while ( ( widget = toBeDeselected.pop() ) ) {
2906 currentlySelected.splice( CKEDITOR.tools.indexOf( currentlySelected, widget ), 1 );
2907 // Widget could be destroyed in the meantime - e.g. data could be set.
2908 if ( widget.isInited() ) {
2909 isDirty = widget.editor.checkDirty();
2910
2911 widget.setSelected( false );
2912
2913 !isDirty && widget.editor.resetDirty();
2914 }
2915 }
2916
2917 if ( focusedChanged && focused ) {
2918 isDirty = widgetsRepo.editor.checkDirty();
2919
2920 widgetsRepo.focused = focused;
2921 widgetsRepo.fire( 'widgetFocused', { widget: focused } );
2922 focused.setFocused( true );
2923
2924 !isDirty && widgetsRepo.editor.resetDirty();
2925 }
2926
2927 while ( ( widget = toBeSelected.pop() ) ) {
2928 currentlySelected.push( widget );
2929 widget.setSelected( true );
2930 }
2931
2932 widgetsRepo.editor.fire( 'unlockSnapshot' );
2933 }
2934 };
2935 }
2936
2937
2938 //
2939 // WIDGET helpers ---------------------------------------------------------
2940 //
2941
2942 // LEFT, RIGHT, UP, DOWN, DEL, BACKSPACE - unblock default fake sel handlers.
2943 var keystrokesNotBlockedByWidget = { 37: 1, 38: 1, 39: 1, 40: 1, 8: 1, 46: 1 };
2944
2945 // Applies or removes style's classes from widget.
2946 // @param {CKEDITOR.style} style Custom widget style.
2947 // @param {Boolean} apply Whether to apply or remove style.
2948 function applyRemoveStyle( widget, style, apply ) {
2949 var changed = 0,
2950 classes = getStyleClasses( style ),
2951 updatedClasses = widget.data.classes || {},
2952 cl;
2953
2954 // Ee... Something is wrong with this style.
2955 if ( !classes )
2956 return;
2957
2958 // Clone, because we need to break reference.
2959 updatedClasses = CKEDITOR.tools.clone( updatedClasses );
2960
2961 while ( ( cl = classes.pop() ) ) {
2962 if ( apply ) {
2963 if ( !updatedClasses[ cl ] )
2964 changed = updatedClasses[ cl ] = 1;
2965 } else {
2966 if ( updatedClasses[ cl ] ) {
2967 delete updatedClasses[ cl ];
2968 changed = 1;
2969 }
2970 }
2971 }
2972 if ( changed )
2973 widget.setData( 'classes', updatedClasses );
2974 }
2975
2976 function cancel( evt ) {
2977 evt.cancel();
2978 }
2979
2980 function copySingleWidget( widget, isCut ) {
2981 var editor = widget.editor,
2982 doc = editor.document;
2983
2984 // We're still handling previous copy/cut.
2985 // When keystroke is used to copy/cut this will also prevent
2986 // conflict with copySingleWidget called again for native copy/cut event.
2987 if ( doc.getById( 'cke_copybin' ) )
2988 return;
2989
2990 // [IE] Use span for copybin and its container to avoid bug with expanding editable height by
2991 // absolutely positioned element.
2992 var copybinName = ( editor.blockless || CKEDITOR.env.ie ) ? 'span' : 'div',
2993 copybin = doc.createElement( copybinName ),
2994 copybinContainer = doc.createElement( copybinName ),
2995 // IE8 always jumps to the end of document.
2996 needsScrollHack = CKEDITOR.env.ie && CKEDITOR.env.version < 9;
2997
2998 copybinContainer.setAttributes( {
2999 id: 'cke_copybin',
3000 'data-cke-temp': '1'
3001 } );
3002
3003 // Position copybin element outside current viewport.
3004 copybin.setStyles( {
3005 position: 'absolute',
3006 width: '1px',
3007 height: '1px',
3008 overflow: 'hidden'
3009 } );
3010
3011 copybin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-5000px' );
3012
3013 var range = editor.createRange();
3014 range.setStartBefore( widget.wrapper );
3015 range.setEndAfter( widget.wrapper );
3016
3017 copybin.setHtml(
3018 '<span data-cke-copybin-start="1">\u200b</span>' +
3019 editor.editable().getHtmlFromRange( range ).getHtml() +
3020 '<span data-cke-copybin-end="1">\u200b</span>' );
3021
3022 // Save snapshot with the current state.
3023 editor.fire( 'saveSnapshot' );
3024
3025 // Ignore copybin.
3026 editor.fire( 'lockSnapshot' );
3027
3028 copybinContainer.append( copybin );
3029 editor.editable().append( copybinContainer );
3030
3031 var listener1 = editor.on( 'selectionChange', cancel, null, null, 0 ),
3032 listener2 = widget.repository.on( 'checkSelection', cancel, null, null, 0 );
3033
3034 if ( needsScrollHack ) {
3035 var docElement = doc.getDocumentElement().$,
3036 scrollTop = docElement.scrollTop;
3037 }
3038
3039 // Once the clone of the widget is inside of copybin, select
3040 // the entire contents. This selection will be copied by the
3041 // native browser's clipboard system.
3042 range = editor.createRange();
3043 range.selectNodeContents( copybin );
3044 range.select();
3045
3046 if ( needsScrollHack )
3047 docElement.scrollTop = scrollTop;
3048
3049 setTimeout( function() {
3050 // [IE] Focus widget before removing copybin to avoid scroll jump.
3051 if ( !isCut )
3052 widget.focus();
3053
3054 copybinContainer.remove();
3055
3056 listener1.removeListener();
3057 listener2.removeListener();
3058
3059 editor.fire( 'unlockSnapshot' );
3060
3061 if ( isCut ) {
3062 widget.repository.del( widget );
3063 editor.fire( 'saveSnapshot' );
3064 }
3065 }, 100 ); // Use 100ms, so Chrome (@Mac) will be able to grab the content.
3066 }
3067
3068 // Extracts classes array from style instance.
3069 function getStyleClasses( style ) {
3070 var attrs = style.getDefinition().attributes,
3071 classes = attrs && attrs[ 'class' ];
3072
3073 return classes ? classes.split( /\s+/ ) : null;
3074 }
3075
3076 // [IE] Force keeping focus because IE sometimes forgets to fire focus on main editable
3077 // when blurring nested editable.
3078 // @context widget
3079 function onEditableBlur() {
3080 var active = CKEDITOR.document.getActive(),
3081 editor = this.editor,
3082 editable = editor.editable();
3083
3084 // If focus stays within editor override blur and set currentActive because it should be
3085 // automatically changed to editable on editable#focus but it is not fired.
3086 if ( ( editable.isInline() ? editable : editor.document.getWindow().getFrame() ).equals( active ) )
3087 editor.focusManager.focus( editable );
3088 }
3089
3090 // Force selectionChange when editable was focused.
3091 // Similar to hack in selection.js#~620.
3092 // @context widget
3093 function onEditableFocus() {
3094 // Gecko does not support 'DOMFocusIn' event on which we unlock selection
3095 // in selection.js to prevent selection locking when entering nested editables.
3096 if ( CKEDITOR.env.gecko )
3097 this.editor.unlockSelection();
3098
3099 // We don't need to force selectionCheck on Webkit, because on Webkit
3100 // we do that on DOMFocusIn in selection.js.
3101 if ( !CKEDITOR.env.webkit ) {
3102 this.editor.forceNextSelectionCheck();
3103 this.editor.selectionChange( 1 );
3104 }
3105 }
3106
3107 // Setup listener on widget#data which will update (remove/add) classes
3108 // by comparing newly set classes with the old ones.
3109 function setupDataClassesListener( widget ) {
3110 // Note: previousClasses and newClasses may be null!
3111 // Tip: for ( cl in null ) is correct.
3112 var previousClasses = null;
3113
3114 widget.on( 'data', function() {
3115 var newClasses = this.data.classes,
3116 cl;
3117
3118 // When setting new classes one need to remember
3119 // that he must break reference.
3120 if ( previousClasses == newClasses )
3121 return;
3122
3123 for ( cl in previousClasses ) {
3124 // Avoid removing and adding classes again.
3125 if ( !( newClasses && newClasses[ cl ] ) )
3126 this.removeClass( cl );
3127 }
3128 for ( cl in newClasses )
3129 this.addClass( cl );
3130
3131 previousClasses = newClasses;
3132 } );
3133 }
3134
3135 // Add a listener to data event that will set/change widget's label (http://dev.ckeditor.com/ticket/14539).
3136 function setupA11yListener( widget ) {
3137 // Note, the function gets executed in a context of widget instance.
3138 function getLabelDefault() {
3139 return this.editor.lang.widget.label.replace( /%1/, this.pathName || this.element.getName() );
3140 }
3141
3142 // Setting a listener on data is enough, there's no need to perform it on widget initialization, as
3143 // setupWidgetData fires this event anyway.
3144 widget.on( 'data', function() {
3145 // In some cases widget might get destroyed in an earlier data listener. For instance, image2 plugin, does
3146 // so when changing its internal state.
3147 if ( !widget.wrapper ) {
3148 return;
3149 }
3150
3151 var label = this.getLabel ? this.getLabel() : getLabelDefault.call( this );
3152
3153 widget.wrapper.setAttribute( 'role', 'region' );
3154 widget.wrapper.setAttribute( 'aria-label', label );
3155 }, null, null, 9999 );
3156 }
3157
3158 function setupDragHandler( widget ) {
3159 if ( !widget.draggable )
3160 return;
3161
3162 var editor = widget.editor,
3163 // Use getLast to find wrapper's direct descendant (http://dev.ckeditor.com/ticket/12022).
3164 container = widget.wrapper.getLast( Widget.isDomDragHandlerContainer ),
3165 img;
3166
3167 // Reuse drag handler if already exists (http://dev.ckeditor.com/ticket/11281).
3168 if ( container )
3169 img = container.findOne( 'img' );
3170 else {
3171 container = new CKEDITOR.dom.element( 'span', editor.document );
3172 container.setAttributes( {
3173 'class': 'cke_reset cke_widget_drag_handler_container',
3174 // Split background and background-image for IE8 which will break on rgba().
3175 style: 'background:rgba(220,220,220,0.5);background-image:url(' + editor.plugins.widget.path + 'images/handle.png)'
3176 } );
3177
3178 img = new CKEDITOR.dom.element( 'img', editor.document );
3179 img.setAttributes( {
3180 'class': 'cke_reset cke_widget_drag_handler',
3181 'data-cke-widget-drag-handler': '1',
3182 src: CKEDITOR.tools.transparentImageData,
3183 width: DRAG_HANDLER_SIZE,
3184 title: editor.lang.widget.move,
3185 height: DRAG_HANDLER_SIZE,
3186 role: 'presentation'
3187 } );
3188 widget.inline && img.setAttribute( 'draggable', 'true' );
3189
3190 container.append( img );
3191 widget.wrapper.append( container );
3192 }
3193
3194 // Preventing page reload when dropped content on widget wrapper (http://dev.ckeditor.com/ticket/13015).
3195 // Widget is not editable so by default drop on it isn't allowed what means that
3196 // browser handles it (there's no editable#drop event). If there's no drop event we cannot block
3197 // the drop, so page is reloaded. This listener enables drop on widget wrappers.
3198 widget.wrapper.on( 'dragover', function( evt ) {
3199 evt.data.preventDefault();
3200 } );
3201
3202 widget.wrapper.on( 'mouseenter', widget.updateDragHandlerPosition, widget );
3203 setTimeout( function() {
3204 widget.on( 'data', widget.updateDragHandlerPosition, widget );
3205 }, 50 );
3206
3207 if ( !widget.inline ) {
3208 img.on( 'mousedown', onBlockWidgetDrag, widget );
3209
3210 // On IE8 'dragstart' is propagated to editable, so editor#dragstart is fired twice on block widgets.
3211 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
3212 img.on( 'dragstart', function( evt ) {
3213 evt.data.preventDefault( true );
3214 } );
3215 }
3216 }
3217
3218 widget.dragHandlerContainer = container;
3219 }
3220
3221 function onBlockWidgetDrag( evt ) {
3222 var finder = this.repository.finder,
3223 locator = this.repository.locator,
3224 liner = this.repository.liner,
3225 editor = this.editor,
3226 editable = editor.editable(),
3227 listeners = [],
3228 sorted = [],
3229 locations,
3230 y;
3231
3232 // Mark dragged widget for repository#finder.
3233 this.repository._.draggedWidget = this;
3234
3235 // Harvest all possible relations and display some closest.
3236 var relations = finder.greedySearch(),
3237
3238 buffer = CKEDITOR.tools.eventsBuffer( 50, function() {
3239 locations = locator.locate( relations );
3240
3241 // There's only a single line displayed for D&D.
3242 sorted = locator.sort( y, 1 );
3243
3244 if ( sorted.length ) {
3245 liner.prepare( relations, locations );
3246 liner.placeLine( sorted[ 0 ] );
3247 liner.cleanup();
3248 }
3249 } );
3250
3251 // Let's have the "dragging cursor" over entire editable.
3252 editable.addClass( 'cke_widget_dragging' );
3253
3254 // Cache mouse position so it is re-used in events buffer.
3255 listeners.push( editable.on( 'mousemove', function( evt ) {
3256 y = evt.data.$.clientY;
3257 buffer.input();
3258 } ) );
3259
3260 // Fire drag start as it happens during the native D&D.
3261 editor.fire( 'dragstart', { target: evt.sender } );
3262
3263 function onMouseUp() {
3264 var l;
3265
3266 buffer.reset();
3267
3268 // Stop observing events.
3269 while ( ( l = listeners.pop() ) )
3270 l.removeListener();
3271
3272 onBlockWidgetDrop.call( this, sorted, evt.sender );
3273 }
3274
3275 // Mouseup means "drop". This is when the widget is being detached
3276 // from DOM and placed at range determined by the line (location).
3277 listeners.push( editor.document.once( 'mouseup', onMouseUp, this ) );
3278
3279 // Prevent calling 'onBlockWidgetDrop' twice in the inline editor.
3280 // `removeListener` does not work if it is called at the same time event is fired.
3281 if ( !editable.isInline() ) {
3282 // Mouseup may occur when user hovers the line, which belongs to
3283 // the outer document. This is, of course, a valid listener too.
3284 listeners.push( CKEDITOR.document.once( 'mouseup', onMouseUp, this ) );
3285 }
3286 }
3287
3288 function onBlockWidgetDrop( sorted, dragTarget ) {
3289 var finder = this.repository.finder,
3290 liner = this.repository.liner,
3291 editor = this.editor,
3292 editable = this.editor.editable();
3293
3294 if ( !CKEDITOR.tools.isEmpty( liner.visible ) ) {
3295 // Retrieve range for the closest location.
3296 var dropRange = finder.getRange( sorted[ 0 ] );
3297
3298 // Focus widget (it could lost focus after mousedown+mouseup)
3299 // and save this state as the one where we want to be taken back when undoing.
3300 this.focus();
3301
3302 // Drag range will be set in the drop listener.
3303 editor.fire( 'drop', {
3304 dropRange: dropRange,
3305 target: dropRange.startContainer
3306 } );
3307 }
3308
3309 // Clean-up custom cursor for editable.
3310 editable.removeClass( 'cke_widget_dragging' );
3311
3312 // Clean-up all remaining lines.
3313 liner.hideVisible();
3314
3315 // Clean-up drag & drop.
3316 editor.fire( 'dragend', { target: dragTarget } );
3317 }
3318
3319 function setupEditables( widget ) {
3320 var editableName,
3321 editableDef,
3322 definedEditables = widget.editables;
3323
3324 widget.editables = {};
3325
3326 if ( !widget.editables )
3327 return;
3328
3329 for ( editableName in definedEditables ) {
3330 editableDef = definedEditables[ editableName ];
3331 widget.initEditable( editableName, typeof editableDef == 'string' ? { selector: editableDef } : editableDef );
3332 }
3333 }
3334
3335 function setupMask( widget ) {
3336 if ( !widget.mask )
3337 return;
3338
3339 // Reuse mask if already exists (http://dev.ckeditor.com/ticket/11281).
3340 var img = widget.wrapper.findOne( '.cke_widget_mask' );
3341
3342 if ( !img ) {
3343 img = new CKEDITOR.dom.element( 'img', widget.editor.document );
3344 img.setAttributes( {
3345 src: CKEDITOR.tools.transparentImageData,
3346 'class': 'cke_reset cke_widget_mask'
3347 } );
3348 widget.wrapper.append( img );
3349 }
3350
3351 widget.mask = img;
3352 }
3353
3354 // Replace parts object containing:
3355 // partName => selector pairs
3356 // with:
3357 // partName => element pairs
3358 function setupParts( widget ) {
3359 if ( widget.parts ) {
3360 var parts = {},
3361 el, partName;
3362
3363 for ( partName in widget.parts ) {
3364 el = widget.wrapper.findOne( widget.parts[ partName ] );
3365 parts[ partName ] = el;
3366 }
3367 widget.parts = parts;
3368 }
3369 }
3370
3371 function setupWidget( widget, widgetDef ) {
3372 setupWrapper( widget );
3373 setupParts( widget );
3374 setupEditables( widget );
3375 setupMask( widget );
3376 setupDragHandler( widget );
3377 setupDataClassesListener( widget );
3378 setupA11yListener( widget );
3379
3380 // http://dev.ckeditor.com/ticket/11145: [IE8] Non-editable content of widget is draggable.
3381 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
3382 widget.wrapper.on( 'dragstart', function( evt ) {
3383 var target = evt.data.getTarget();
3384
3385 // Allow text dragging inside nested editables or dragging inline widget's drag handler.
3386 if ( !Widget.getNestedEditable( widget, target ) && !( widget.inline && Widget.isDomDragHandler( target ) ) )
3387 evt.data.preventDefault();
3388 } );
3389 }
3390
3391 widget.wrapper.removeClass( 'cke_widget_new' );
3392 widget.element.addClass( 'cke_widget_element' );
3393
3394 widget.on( 'key', function( evt ) {
3395 var keyCode = evt.data.keyCode;
3396
3397 // ENTER.
3398 if ( keyCode == 13 ) {
3399 widget.edit();
3400 // CTRL+C or CTRL+X.
3401 } else if ( keyCode == CKEDITOR.CTRL + 67 || keyCode == CKEDITOR.CTRL + 88 ) {
3402 copySingleWidget( widget, keyCode == CKEDITOR.CTRL + 88 );
3403 return; // Do not preventDefault.
3404 } else if ( keyCode in keystrokesNotBlockedByWidget || ( CKEDITOR.CTRL & keyCode ) || ( CKEDITOR.ALT & keyCode ) ) {
3405 // Pass chosen keystrokes to other plugins or default fake sel handlers.
3406 // Pass all CTRL/ALT keystrokes.
3407 return;
3408 }
3409
3410 return false;
3411 }, null, null, 999 );
3412 // Listen with high priority so it's possible
3413 // to overwrite this callback.
3414
3415 widget.on( 'doubleclick', function( evt ) {
3416 if ( widget.edit() ) {
3417 // We have to cancel event if edit method opens a dialog, otherwise
3418 // link plugin may open extra dialog (http://dev.ckeditor.com/ticket/12140).
3419 evt.cancel();
3420 }
3421 } );
3422
3423 if ( widgetDef.data )
3424 widget.on( 'data', widgetDef.data );
3425
3426 if ( widgetDef.edit )
3427 widget.on( 'edit', widgetDef.edit );
3428 }
3429
3430 function setupWidgetData( widget, startupData ) {
3431 var widgetDataAttr = widget.element.data( 'cke-widget-data' );
3432
3433 if ( widgetDataAttr )
3434 widget.setData( JSON.parse( decodeURIComponent( widgetDataAttr ) ) );
3435 if ( startupData )
3436 widget.setData( startupData );
3437
3438 // Populate classes if they are not preset.
3439 if ( !widget.data.classes )
3440 widget.setData( 'classes', widget.getClasses() );
3441
3442 // Unblock data and...
3443 widget.dataReady = true;
3444
3445 // Write data to element because this was blocked when data wasn't ready.
3446 writeDataToElement( widget );
3447
3448 // Fire data event first time, because this was blocked when data wasn't ready.
3449 widget.fire( 'data', widget.data );
3450 }
3451
3452 function setupWrapper( widget ) {
3453 // Retrieve widget wrapper. Assign an id to it.
3454 var wrapper = widget.wrapper = widget.element.getParent();
3455 wrapper.setAttribute( 'data-cke-widget-id', widget.id );
3456 }
3457
3458 function writeDataToElement( widget ) {
3459 widget.element.data( 'cke-widget-data', encodeURIComponent( JSON.stringify( widget.data ) ) );
3460 }
3461
3462 //
3463 // WIDGET STYLE HANDLER ---------------------------------------------------
3464 //
3465
3466 ( function() {
3467 // Styles categorized by group. It is used to prevent applying styles for the same group being used together.
3468 var styleGroups = {};
3469
3470 /**
3471 * The class representing a widget style. It is an {@link CKEDITOR#STYLE_OBJECT object} like
3472 * the styles handler for widgets.
3473 *
3474 * **Note:** This custom style handler does not support all methods of the {@link CKEDITOR.style} class.
3475 * Not supported methods: {@link #applyToRange}, {@link #removeFromRange}, {@link #applyToObject}.
3476 *
3477 * @since 4.4
3478 * @class CKEDITOR.style.customHandlers.widget
3479 * @extends CKEDITOR.style
3480 */
3481 CKEDITOR.style.addCustomHandler( {
3482 type: 'widget',
3483
3484 setup: function( styleDefinition ) {
3485 /**
3486 * The name of widget to which this style can be applied.
3487 * It is extracted from style definition's `widget` property.
3488 *
3489 * @property {String} widget
3490 */
3491 this.widget = styleDefinition.widget;
3492
3493 /**
3494 * An array of groups that this style belongs to.
3495 * Styles assigned to the same group cannot be combined.
3496 *
3497 * @since 4.6.2
3498 * @property {Array} group
3499 */
3500 this.group = typeof styleDefinition.group == 'string' ? [ styleDefinition.group ] : styleDefinition.group;
3501
3502 // Store style categorized by its group.
3503 // It is used to prevent enabling two styles from same group.
3504 if ( this.group ) {
3505 saveStyleGroup( this );
3506 }
3507 },
3508
3509 apply: function( editor ) {
3510 var widget;
3511
3512 // Before CKEditor 4.4 wasn't a required argument, so we need to
3513 // handle a case when it wasn't provided.
3514 if ( !( editor instanceof CKEDITOR.editor ) )
3515 return;
3516
3517 // Theoretically we could bypass checkApplicable, get widget from
3518 // widgets.focused and check its name, what would be faster, but then
3519 // this custom style would work differently than the default style
3520 // which checks if it's applicable before applying or removing itself.
3521 if ( this.checkApplicable( editor.elementPath(), editor ) ) {
3522 widget = editor.widgets.focused;
3523
3524 // Remove other styles from the same group.
3525 if ( this.group ) {
3526 this.removeStylesFromSameGroup( editor );
3527 }
3528
3529 widget.applyStyle( this );
3530 }
3531 },
3532
3533 remove: function( editor ) {
3534 // Before CKEditor 4.4 wasn't a required argument, so we need to
3535 // handle a case when it wasn't provided.
3536 if ( !( editor instanceof CKEDITOR.editor ) )
3537 return;
3538
3539 if ( this.checkApplicable( editor.elementPath(), editor ) )
3540 editor.widgets.focused.removeStyle( this );
3541 },
3542
3543 /**
3544 * Removes all styles that belong to the same group as this style. This method will neither add nor remove
3545 * the current style.
3546 * Returns `true` if any style was removed, otherwise returns `false`.
3547 *
3548 * @since 4.6.2
3549 * @param {CKEDITOR.editor} editor
3550 * @returns {Boolean}
3551 */
3552 removeStylesFromSameGroup: function( editor ) {
3553 var stylesFromSameGroup,
3554 path,
3555 removed = false;
3556
3557 // Before CKEditor 4.4 wasn't a required argument, so we need to
3558 // handle a case when it wasn't provided.
3559 if ( !( editor instanceof CKEDITOR.editor ) )
3560 return false;
3561
3562 path = editor.elementPath();
3563 if ( this.checkApplicable( path, editor ) ) {
3564 // Iterate over each group.
3565 for ( var i = 0, l = this.group.length; i < l; i++ ) {
3566 stylesFromSameGroup = styleGroups[ this.widget ][ this.group[ i ] ];
3567 // Iterate over each style from group.
3568 for ( var j = 0; j < stylesFromSameGroup.length; j++ ) {
3569 if ( stylesFromSameGroup[ j ] !== this && stylesFromSameGroup[ j ].checkActive( path, editor ) ) {
3570 editor.widgets.focused.removeStyle( stylesFromSameGroup[ j ] );
3571 removed = true;
3572 }
3573 }
3574 }
3575 }
3576
3577 return removed;
3578 },
3579
3580 checkActive: function( elementPath, editor ) {
3581 return this.checkElementMatch( elementPath.lastElement, 0, editor );
3582 },
3583
3584 checkApplicable: function( elementPath, editor ) {
3585 // Before CKEditor 4.4 wasn't a required argument, so we need to
3586 // handle a case when it wasn't provided.
3587 if ( !( editor instanceof CKEDITOR.editor ) )
3588 return false;
3589
3590 return this.checkElement( elementPath.lastElement );
3591 },
3592
3593 checkElementMatch: checkElementMatch,
3594
3595 checkElementRemovable: checkElementMatch,
3596
3597 /**
3598 * Checks if an element is a {@link CKEDITOR.plugins.widget#wrapper wrapper} of a
3599 * widget whose name matches the {@link #widget widget name} specified in the style definition.
3600 *
3601 * @param {CKEDITOR.dom.element} element
3602 * @returns {Boolean}
3603 */
3604 checkElement: function( element ) {
3605 if ( !Widget.isDomWidgetWrapper( element ) )
3606 return false;
3607
3608 var widgetElement = element.getFirst( Widget.isDomWidgetElement );
3609 return widgetElement && widgetElement.data( 'widget' ) == this.widget;
3610 },
3611
3612 buildPreview: function( label ) {
3613 return label || this._.definition.name;
3614 },
3615
3616 /**
3617 * Returns allowed content rules which should be registered for this style.
3618 * Uses widget's {@link CKEDITOR.plugins.widget.definition#styleableElements} to make a rule
3619 * allowing classes on specified elements or use widget's
3620 * {@link CKEDITOR.plugins.widget.definition#styleToAllowedContentRules} method to transform a style
3621 * into allowed content rules.
3622 *
3623 * @param {CKEDITOR.editor} The editor instance.
3624 * @returns {CKEDITOR.filter.allowedContentRules}
3625 */
3626 toAllowedContentRules: function( editor ) {
3627 if ( !editor )
3628 return null;
3629
3630 var widgetDef = editor.widgets.registered[ this.widget ],
3631 classes,
3632 rule = {};
3633
3634 if ( !widgetDef )
3635 return null;
3636
3637 if ( widgetDef.styleableElements ) {
3638 classes = this.getClassesArray();
3639 if ( !classes )
3640 return null;
3641
3642 rule[ widgetDef.styleableElements ] = {
3643 classes: classes,
3644 propertiesOnly: true
3645 };
3646 return rule;
3647 }
3648 if ( widgetDef.styleToAllowedContentRules )
3649 return widgetDef.styleToAllowedContentRules( this );
3650 return null;
3651 },
3652
3653 /**
3654 * Returns classes defined in the style in form of an array.
3655 *
3656 * @returns {String[]}
3657 */
3658 getClassesArray: function() {
3659 var classes = this._.definition.attributes && this._.definition.attributes[ 'class' ];
3660
3661 return classes ? CKEDITOR.tools.trim( classes ).split( /\s+/ ) : null;
3662 },
3663
3664 /**
3665 * Not implemented.
3666 *
3667 * @method applyToRange
3668 */
3669 applyToRange: notImplemented,
3670
3671 /**
3672 * Not implemented.
3673 *
3674 * @method removeFromRange
3675 */
3676 removeFromRange: notImplemented,
3677
3678 /**
3679 * Not implemented.
3680 *
3681 * @method applyToObject
3682 */
3683 applyToObject: notImplemented
3684 } );
3685
3686 function notImplemented() {}
3687
3688 // @context style
3689 function checkElementMatch( element, fullMatch, editor ) {
3690 // Before CKEditor 4.4 wasn't a required argument, so we need to
3691 // handle a case when it wasn't provided.
3692 if ( !editor )
3693 return false;
3694
3695 if ( !this.checkElement( element ) )
3696 return false;
3697
3698 var widget = editor.widgets.getByElement( element, true );
3699 return widget && widget.checkStyleActive( this );
3700 }
3701
3702 // Save and categorize style by its group.
3703 function saveStyleGroup( style ) {
3704 var widgetName = style.widget,
3705 group;
3706
3707 if ( !styleGroups[ widgetName ] ) {
3708 styleGroups[ widgetName ] = {};
3709 }
3710
3711 for ( var i = 0, l = style.group.length; i < l; i++ ) {
3712 group = style.group[ i ];
3713 if ( !styleGroups[ widgetName ][ group ] ) {
3714 styleGroups[ widgetName ][ group ] = [];
3715 }
3716
3717 styleGroups[ widgetName ][ group ].push( style );
3718 }
3719 }
3720
3721 } )();
3722
3723 //
3724 // EXPOSE PUBLIC API ------------------------------------------------------
3725 //
3726
3727 CKEDITOR.plugins.widget = Widget;
3728 Widget.repository = Repository;
3729 Widget.nestedEditable = NestedEditable;
3730} )();
3731
3732/**
3733 * An event fired when a widget definition is registered by the {@link CKEDITOR.plugins.widget.repository#add} method.
3734 * It is possible to modify the definition being registered.
3735 *
3736 * @event widgetDefinition
3737 * @member CKEDITOR.editor
3738 * @param {CKEDITOR.plugins.widget.definition} data Widget definition.
3739 */
3740
3741/**
3742 * This is an abstract class that describes the definition of a widget.
3743 * It is a type of {@link CKEDITOR.plugins.widget.repository#add} method's second argument.
3744 *
3745 * Widget instances inherit from registered widget definitions, although not in a prototypal way.
3746 * They are simply extended with corresponding widget definitions. Note that not all properties of
3747 * the widget definition become properties of a widget. Some, like {@link #data} or {@link #edit}, become
3748 * widget's events listeners.
3749 *
3750 * @class CKEDITOR.plugins.widget.definition
3751 * @abstract
3752 * @mixins CKEDITOR.feature
3753 */
3754
3755/**
3756 * Widget definition name. It is automatically set when the definition is
3757 * {@link CKEDITOR.plugins.widget.repository#add registered}.
3758 *
3759 * @property {String} name
3760 */
3761
3762/**
3763 * The method executed while initializing a widget, after a widget instance
3764 * is created, but before it is ready. It is executed before the first
3765 * {@link CKEDITOR.plugins.widget#event-data} is fired so it is common to
3766 * use the `init` method to populate widget data with information loaded from
3767 * the DOM, like for exmaple:
3768 *
3769 * init: function() {
3770 * this.setData( 'width', this.element.getStyle( 'width' ) );
3771 *
3772 * if ( this.parts.caption.getStyle( 'display' ) != 'none' )
3773 * this.setData( 'showCaption', true );
3774 * }
3775 *
3776 * @property {Function} init
3777 */
3778
3779/**
3780 * The function to be used to upcast an element to this widget or a
3781 * comma-separated list of upcast methods from the {@link #upcasts} object.
3782 *
3783 * The upcast function **is not** executed in the widget context (because the widget
3784 * does not exist yet) and two arguments are passed:
3785 *
3786 * * `element` ({@link CKEDITOR.htmlParser.element}) &ndash; The element to be checked.
3787 * * `data` (`Object`) &ndash; The object which can be extended with data which will then be passed to the widget.
3788 *
3789 * An element will be upcasted if a function returned `true` or an instance of
3790 * a {@link CKEDITOR.htmlParser.element} if upcasting meant DOM structure changes
3791 * (in this case the widget will be initialized on the returned element).
3792 *
3793 * @property {String/Function} upcast
3794 */
3795
3796/**
3797 * The object containing functions which can be used to upcast this widget.
3798 * Only those pointed by the {@link #upcast} property will be used.
3799 *
3800 * In most cases it is appropriate to use {@link #upcast} directly,
3801 * because majority of widgets need just one method.
3802 * However, in some cases the widget author may want to expose more than one variant
3803 * and then this property may be used.
3804 *
3805 * upcasts: {
3806 * // This function may upcast only figure elements.
3807 * figure: function() {
3808 * // ...
3809 * },
3810 * // This function may upcast only image elements.
3811 * image: function() {
3812 * // ...
3813 * },
3814 * // More variants...
3815 * }
3816 *
3817 * // Then, widget user may choose which upcast methods will be enabled.
3818 * editor.on( 'widgetDefinition', function( evt ) {
3819 * if ( evt.data.name == 'image' )
3820 * evt.data.upcast = 'figure,image'; // Use both methods.
3821 * } );
3822 *
3823 * @property {Object} upcasts
3824 */
3825
3826/**
3827 * The {@link #upcast} method(s) priority. The upcast with a lower priority number will be called before
3828 * the one with a higher number. The default priority is `10`.
3829 *
3830 * @since 4.5
3831 * @property {Number} [upcastPriority=10]
3832 */
3833
3834/**
3835 * The function to be used to downcast this widget or
3836 * a name of the downcast option from the {@link #downcasts} object.
3837 *
3838 * The downcast funciton will be executed in the {@link CKEDITOR.plugins.widget} context
3839 * and with `widgetElement` ({@link CKEDITOR.htmlParser.element}) argument which is
3840 * the widget's main element.
3841 *
3842 * The function may return an instance of the {@link CKEDITOR.htmlParser.node} class if the widget
3843 * needs to be downcasted to a different node than the widget's main element.
3844 *
3845 * @property {String/Function} downcast
3846 */
3847
3848/**
3849 * The object containing functions which can be used to downcast this widget.
3850 * Only the one pointed by the {@link #downcast} property will be used.
3851 *
3852 * In most cases it is appropriate to use {@link #downcast} directly,
3853 * because majority of widgets have just one variant of downcasting (or none at all).
3854 * However, in some cases the widget author may want to expose more than one variant
3855 * and then this property may be used.
3856 *
3857 * downcasts: {
3858 * // This downcast may transform the widget into the figure element.
3859 * figure: function() {
3860 * // ...
3861 * },
3862 * // This downcast may transform the widget into the image element with data-* attributes.
3863 * image: function() {
3864 * // ...
3865 * }
3866 * }
3867 *
3868 * // Then, the widget user may choose one of the downcast options when setting up his editor.
3869 * editor.on( 'widgetDefinition', function( evt ) {
3870 * if ( evt.data.name == 'image' )
3871 * evt.data.downcast = 'figure';
3872 * } );
3873 *
3874 * @property downcasts
3875 */
3876
3877/**
3878 * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-edit} event listener.
3879 * This means that it will be executed when a widget is being edited.
3880 * See the {@link CKEDITOR.plugins.widget#method-edit} method.
3881 *
3882 * @property {Function} edit
3883 */
3884
3885/**
3886 * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-data} event listener.
3887 * This means that it will be executed every time the {@link CKEDITOR.plugins.widget#property-data widget data} changes.
3888 *
3889 * @property {Function} data
3890 */
3891
3892/**
3893 * The method to be executed when the widget's command is executed in order to insert a new widget
3894 * (widget of this type is not focused). If not defined, then the default action will be
3895 * performed which means that:
3896 *
3897 * * An instance of the widget will be created in a detached {@link CKEDITOR.dom.documentFragment document fragment},
3898 * * The {@link CKEDITOR.plugins.widget#method-edit} method will be called to trigger widget editing,
3899 * * The widget element will be inserted into DOM.
3900 *
3901 * @property {Function} insert
3902 */
3903
3904/**
3905 * The name of a dialog window which will be opened on {@link CKEDITOR.plugins.widget#method-edit}.
3906 * If not defined, then the {@link CKEDITOR.plugins.widget#method-edit} method will not perform any action and
3907 * widget's command will insert a new widget without opening a dialog window first.
3908 *
3909 * @property {String} dialog
3910 */
3911
3912/**
3913 * The template which will be used to create a new widget element (when the widget's command is executed).
3914 * This string is populated with {@link #defaults default values} by using the {@link CKEDITOR.template} format.
3915 * Therefore it has to be a valid {@link CKEDITOR.template} argument.
3916 *
3917 * @property {String} template
3918 */
3919
3920/**
3921 * The data object which will be used to populate the data of a newly created widget.
3922 * See {@link CKEDITOR.plugins.widget#property-data}.
3923 *
3924 * defaults: {
3925 * showCaption: true,
3926 * align: 'none'
3927 * }
3928 *
3929 * @property defaults
3930 */
3931
3932/**
3933 * An object containing definitions of widget components (part name => CSS selector).
3934 *
3935 * parts: {
3936 * image: 'img',
3937 * caption: 'div.caption'
3938 * }
3939 *
3940 * @property parts
3941 */
3942
3943/**
3944 * An object containing definitions of nested editables (editable name => {@link CKEDITOR.plugins.widget.nestedEditable.definition}).
3945 * Note that editables *have to* be defined in the same order as they are in DOM / {@link CKEDITOR.plugins.widget.definition#template template}.
3946 * Otherwise errors will occur when nesting widgets inside each other.
3947 *
3948 * editables: {
3949 * header: 'h1',
3950 * content: {
3951 * selector: 'div.content',
3952 * allowedContent: 'p strong em; a[!href]'
3953 * }
3954 * }
3955 *
3956 * @property editables
3957 */
3958
3959/**
3960 * The function used to obtain an accessibility label for the widget. It might be used to make
3961 * the widget labels as precise as possible, since it has access to the widget instance.
3962 *
3963 * If not specified, the default implementation will use the {@link #pathName} or the main
3964 * {@link CKEDITOR.plugins.widget#element element} tag name.
3965 *
3966 * @property {Function} getLabel
3967 */
3968
3969/**
3970 * The widget name displayed in the elements path.
3971 *
3972 * @property {String} pathName
3973 */
3974
3975/**
3976 * If set to `true`, the widget's element will be covered with a transparent mask.
3977 * This will prevent its content from being clickable, which matters in case
3978 * of special elements like embedded Flash or iframes that generate a separate "context".
3979 *
3980 * @property {Boolean} mask
3981 */
3982
3983/**
3984 * If set to `true/false`, it will force the widget to be either an inline or a block widget.
3985 * If not set, the widget type will be determined from the widget element.
3986 *
3987 * Widget type influences whether a block (`div`) or an inline (`span`) element is used
3988 * for the wrapper.
3989 *
3990 * @property {Boolean} inline
3991 */
3992
3993/**
3994 * The label for the widget toolbar button.
3995 *
3996 * editor.widgets.add( 'simplebox', {
3997 * button: 'Create a simple box'
3998 * } );
3999 *
4000 * editor.widgets.add( 'simplebox', {
4001 * button: editor.lang.simplebox.title
4002 * } );
4003 *
4004 * @property {String} button
4005 */
4006
4007/**
4008 * Whether widget should be draggable. Defaults to `true`.
4009 * If set to `false` drag handler will not be displayed when hovering widget.
4010 *
4011 * @property {Boolean} draggable
4012 */
4013
4014/**
4015 * Names of element(s) (separated by spaces) for which the {@link CKEDITOR.filter} should allow classes
4016 * defined in the widget styles. For example if your widget is upcasted from a simple `<div>`
4017 * element, then in order to make it styleable you can set:
4018 *
4019 * editor.widgets.add( 'customWidget', {
4020 * upcast: function( element ) {
4021 * return element.name == 'div';
4022 * },
4023 *
4024 * // ...
4025 *
4026 * styleableElements: 'div'
4027 * } );
4028 *
4029 * Then, when the following style is defined:
4030 *
4031 * {
4032 * name: 'Thick border', type: 'widget', widget: 'customWidget',
4033 * attributes: { 'class': 'thickBorder' }
4034 * }
4035 *
4036 * a rule allowing the `thickBorder` class for `div` elements will be registered in the {@link CKEDITOR.filter}.
4037 *
4038 * If you need to have more freedom when transforming widget style to allowed content rules,
4039 * you can use the {@link #styleToAllowedContentRules} callback.
4040 *
4041 * @since 4.4
4042 * @property {String} styleableElements
4043 */
4044
4045/**
4046 * Function transforming custom widget's {@link CKEDITOR.style} instance into
4047 * {@link CKEDITOR.filter.allowedContentRules}. It may be used when a static
4048 * {@link #styleableElements} property is not enough to inform the {@link CKEDITOR.filter}
4049 * what HTML features should be enabled when allowing the given style.
4050 *
4051 * In most cases, when style's classes just have to be added to element name(s) used by
4052 * the widget element, it is recommended to use simpler {@link #styleableElements} property.
4053 *
4054 * In order to get parsed classes from the style definition you can use
4055 * {@link CKEDITOR.style.customHandlers.widget#getClassesArray}.
4056 *
4057 * For example, if you want to use the [object format of allowed content rules](#!/guide/dev_allowed_content_rules-section-object-format),
4058 * to specify `match` validator, your implementation could look like this:
4059 *
4060 * editor.widgets.add( 'customWidget', {
4061 * // ...
4062 *
4063 * styleToAllowedContentRules: funciton( style ) {
4064 * // Retrieve classes defined in the style.
4065 * var classes = style.getClassesArray();
4066 *
4067 * // Do something crazy - for example return allowed content rules in object format,
4068 * // with custom match property and propertiesOnly flag.
4069 * return {
4070 * h1: {
4071 * match: isWidgetElement,
4072 * propertiesOnly: true,
4073 * classes: classes
4074 * }
4075 * };
4076 * }
4077 * } );
4078 *
4079 * @since 4.4
4080 * @property {Function} styleToAllowedContentRules
4081 * @param {CKEDITOR.style.customHandlers.widget} style The style to be transformed.
4082 * @returns {CKEDITOR.filter.allowedContentRules}
4083 */
4084
4085/**
4086 * This is an abstract class that describes the definition of a widget's nested editable.
4087 * It is a type of values in the {@link CKEDITOR.plugins.widget.definition#editables} object.
4088 *
4089 * In the simplest case the definition is a string which is a CSS selector used to
4090 * find an element that will become a nested editable inside the widget. Note that
4091 * the widget element can be a nested editable, too.
4092 *
4093 * In the more advanced case a definition is an object with a required `selector` property.
4094 *
4095 * editables: {
4096 * header: 'h1',
4097 * content: {
4098 * selector: 'div.content',
4099 * allowedContent: 'p strong em; a[!href]'
4100 * }
4101 * }
4102 *
4103 * @class CKEDITOR.plugins.widget.nestedEditable.definition
4104 * @abstract
4105 */
4106
4107/**
4108 * The CSS selector used to find an element which will become a nested editable.
4109 *
4110 * @property {String} selector
4111 */
4112
4113/**
4114 * The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules
4115 * which will be used to limit the content allowed in this nested editable.
4116 * This option is similar to {@link CKEDITOR.config#allowedContent} and one can
4117 * use it to limit the editor features available in the nested editable.
4118 *
4119 * @property {CKEDITOR.filter.allowedContentRules} allowedContent
4120 */
4121
4122/**
4123 * Nested editable name displayed in elements path.
4124 *
4125 * @property {String} pathName
4126 */
diff --git a/app/assets/javascripts/ckeditor/plugins/widgetselection/plugin.js b/app/assets/javascripts/ckeditor/plugins/widgetselection/plugin.js new file mode 100644 index 0000000..e21ea88 --- /dev/null +++ b/app/assets/javascripts/ckeditor/plugins/widgetselection/plugin.js
@@ -0,0 +1,366 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6/**
7 * @fileOverview A plugin created to handle ticket http://dev.ckeditor.com/ticket/11064. While the issue is caused by native WebKit/Blink behaviour,
8 * this plugin can be easily detached or modified when the issue is fixed in the browsers without changing the core.
9 * When Ctrl/Cmd + A is pressed to select all content it does not work due to a bug in
10 * Webkit/Blink if a non-editable element is at the beginning or the end of the content.
11 */
12
13( function() {
14 'use strict';
15
16 CKEDITOR.plugins.add( 'widgetselection', {
17
18 init: function( editor ) {
19 if ( CKEDITOR.env.webkit ) {
20 var widgetselection = CKEDITOR.plugins.widgetselection;
21
22 editor.on( 'contentDom', function( evt ) {
23
24 var editor = evt.editor,
25 doc = editor.document,
26 editable = editor.editable();
27
28 editable.attachListener( doc, 'keydown', function( evt ) {
29 var data = evt.data.$;
30
31 // Ctrl/Cmd + A
32 if ( evt.data.getKey() == 65 && ( CKEDITOR.env.mac && data.metaKey || !CKEDITOR.env.mac && data.ctrlKey ) ) {
33
34 // Defer the call so the selection is already changed by the pressed keys.
35 CKEDITOR.tools.setTimeout( function() {
36
37 // Manage filler elements on keydown. If there is no need
38 // to add fillers, we need to check and clean previously used once.
39 if ( !widgetselection.addFillers( editable ) ) {
40 widgetselection.removeFillers( editable );
41 }
42 }, 0 );
43 }
44 }, null, null, -1 );
45
46 // Check and clean previously used fillers.
47 editor.on( 'selectionCheck', function( evt ) {
48 widgetselection.removeFillers( evt.editor.editable() );
49 } );
50
51 // Remove fillers on paste before data gets inserted into editor.
52 editor.on( 'paste', function( evt ) {
53 evt.data.dataValue = widgetselection.cleanPasteData( evt.data.dataValue );
54 } );
55
56 if ( 'selectall' in editor.plugins ) {
57 widgetselection.addSelectAllIntegration( editor );
58 }
59 } );
60 }
61 }
62 } );
63
64 /**
65 * A set of helper methods for the Widget Selection plugin.
66 *
67 * @property widgetselection
68 * @member CKEDITOR.plugins
69 * @since 4.6.1
70 */
71 CKEDITOR.plugins.widgetselection = {
72
73 /**
74 * The start filler element reference.
75 *
76 * @property {CKEDITOR.dom.element}
77 * @member CKEDITOR.plugins.widgetselection
78 * @private
79 */
80 startFiller: null,
81
82 /**
83 * The end filler element reference.
84 *
85 * @property {CKEDITOR.dom.element}
86 * @member CKEDITOR.plugins.widgetselection
87 * @private
88 */
89 endFiller: null,
90
91 /**
92 * An attribute which identifies the filler element.
93 *
94 * @property {String}
95 * @member CKEDITOR.plugins.widgetselection
96 * @private
97 */
98 fillerAttribute: 'data-cke-filler-webkit',
99
100 /**
101 * The default content of the filler element. Note: The filler needs to have `visible` content.
102 * Unprintable elements or empty content do not help as a workaround.
103 *
104 * @property {String}
105 * @member CKEDITOR.plugins.widgetselection
106 * @private
107 */
108 fillerContent: '&nbsp;',
109
110 /**
111 * Tag name which is used to create fillers.
112 *
113 * @property {String}
114 * @member CKEDITOR.plugins.widgetselection
115 * @private
116 */
117 fillerTagName: 'div',
118
119 /**
120 * Adds a filler before or after a non-editable element at the beginning or the end of the `editable`.
121 *
122 * @param {CKEDITOR.editable} editable
123 * @returns {Boolean}
124 * @member CKEDITOR.plugins.widgetselection
125 */
126 addFillers: function( editable ) {
127 var editor = editable.editor;
128
129 // Whole content should be selected, if not fix the selection manually.
130 if ( !this.isWholeContentSelected( editable ) && editable.getChildCount() > 0 ) {
131
132 var firstChild = editable.getFirst( filterTempElements ),
133 lastChild = editable.getLast( filterTempElements );
134
135 // Check if first element is editable. If not prepend with filler.
136 if ( firstChild && firstChild.type == CKEDITOR.NODE_ELEMENT && !firstChild.isEditable() ) {
137 this.startFiller = this.createFiller();
138 editable.append( this.startFiller, 1 );
139 }
140
141 // Check if last element is editable. If not append filler.
142 if ( lastChild && lastChild.type == CKEDITOR.NODE_ELEMENT && !lastChild.isEditable() ) {
143 this.endFiller = this.createFiller( true );
144 editable.append( this.endFiller, 0 );
145 }
146
147 // Reselect whole content after any filler was added.
148 if ( this.hasFiller( editable ) ) {
149 var rangeAll = editor.createRange();
150 rangeAll.selectNodeContents( editable );
151 rangeAll.select();
152 return true;
153 }
154 }
155 return false;
156 },
157
158 /**
159 * Removes filler elements or updates their references.
160 *
161 * It will **not remove** filler elements if the whole content is selected, as it would break the
162 * selection.
163 *
164 * @param {CKEDITOR.editable} editable
165 * @member CKEDITOR.plugins.widgetselection
166 */
167 removeFillers: function( editable ) {
168 // If startFiller or endFiller exists and not entire content is selected it means the selection
169 // just changed from selected all. We need to remove fillers and set proper selection/content.
170 if ( this.hasFiller( editable ) && !this.isWholeContentSelected( editable ) ) {
171
172 var startFillerContent = editable.findOne( this.fillerTagName + '[' + this.fillerAttribute + '=start]' ),
173 endFillerContent = editable.findOne( this.fillerTagName + '[' + this.fillerAttribute + '=end]' );
174
175 if ( this.startFiller && startFillerContent && this.startFiller.equals( startFillerContent ) ) {
176 this.removeFiller( this.startFiller, editable );
177 } else {
178 // The start filler is still present but it is a different element than previous one. It means the
179 // undo recreating entirely selected content was performed. We need to update filler reference.
180 this.startFiller = startFillerContent;
181 }
182
183 if ( this.endFiller && endFillerContent && this.endFiller.equals( endFillerContent ) ) {
184 this.removeFiller( this.endFiller, editable );
185 } else {
186 // Same as with start filler.
187 this.endFiller = endFillerContent;
188 }
189 }
190 },
191
192 /**
193 * Removes fillers from the paste data.
194 *
195 * @param {String} data
196 * @returns {String}
197 * @member CKEDITOR.plugins.widgetselection
198 * @private
199 */
200 cleanPasteData: function( data ) {
201 if ( data && data.length ) {
202 data = data
203 .replace( this.createFillerRegex(), '' )
204 .replace( this.createFillerRegex( true ), '' );
205 }
206 return data;
207 },
208
209 /**
210 * Checks if the entire content of the given editable is selected.
211 *
212 * @param {CKEDITOR.editable} editable
213 * @returns {Boolean}
214 * @member CKEDITOR.plugins.widgetselection
215 * @private
216 */
217 isWholeContentSelected: function( editable ) {
218
219 var range = editable.editor.getSelection().getRanges()[ 0 ];
220 if ( range ) {
221
222 if ( range && range.collapsed ) {
223 return false;
224
225 } else {
226 var rangeClone = range.clone();
227 rangeClone.enlarge( CKEDITOR.ENLARGE_ELEMENT );
228
229 return !!( rangeClone && editable && rangeClone.startContainer && rangeClone.endContainer &&
230 rangeClone.startOffset === 0 && rangeClone.endOffset === editable.getChildCount() &&
231 rangeClone.startContainer.equals( editable ) && rangeClone.endContainer.equals( editable ) );
232 }
233 }
234 return false;
235 },
236
237 /**
238 * Checks if there is any filler element in the given editable.
239 *
240 * @param {CKEDITOR.editable} editable
241 * @returns {Boolean}
242 * @member CKEDITOR.plugins.widgetselection
243 * @private
244 */
245 hasFiller: function( editable ) {
246 return editable.find( this.fillerTagName + '[' + this.fillerAttribute + ']' ).count() > 0;
247 },
248
249 /**
250 * Creates a filler element.
251 *
252 * @param {Boolean} [onEnd] If filler will be placed on end or beginning of the content.
253 * @returns {CKEDITOR.dom.element}
254 * @member CKEDITOR.plugins.widgetselection
255 * @private
256 */
257 createFiller: function( onEnd ) {
258 var filler = new CKEDITOR.dom.element( this.fillerTagName );
259 filler.setHtml( this.fillerContent );
260 filler.setAttribute( this.fillerAttribute, onEnd ? 'end' : 'start' );
261 filler.setAttribute( 'data-cke-temp', 1 );
262 filler.setStyles( {
263 display: 'block',
264 width: 0,
265 height: 0,
266 padding: 0,
267 border: 0,
268 margin: 0,
269 position: 'absolute',
270 top: 0,
271 left: '-9999px',
272 opacity: 0,
273 overflow: 'hidden'
274 } );
275
276 return filler;
277 },
278
279 /**
280 * Removes the specific filler element from the given editable. If the filler contains any content (typed or pasted),
281 * it replaces the current editable content. If not, the caret is placed before the first or after the last editable
282 * element (depends if the filler was at the beginning or the end).
283 *
284 * @param {CKEDITOR.dom.element} filler
285 * @param {CKEDITOR.editable} editable
286 * @member CKEDITOR.plugins.widgetselection
287 * @private
288 */
289 removeFiller: function( filler, editable ) {
290 if ( filler ) {
291 var editor = editable.editor,
292 currentRange = editable.editor.getSelection().getRanges()[ 0 ],
293 currentPath = currentRange.startPath(),
294 range = editor.createRange(),
295 insertedHtml,
296 fillerOnStart,
297 manuallyHandleCaret;
298
299 if ( currentPath.contains( filler ) ) {
300 insertedHtml = filler.getHtml();
301 manuallyHandleCaret = true;
302 }
303
304 fillerOnStart = filler.getAttribute( this.fillerAttribute ) == 'start';
305 filler.remove();
306 filler = null;
307
308 if ( insertedHtml && insertedHtml.length > 0 && insertedHtml != this.fillerContent ) {
309 editable.insertHtmlIntoRange( insertedHtml, editor.getSelection().getRanges()[ 0 ] );
310 range.setStartAt( editable.getChild( editable.getChildCount() - 1 ), CKEDITOR.POSITION_BEFORE_END );
311 editor.getSelection().selectRanges( [ range ] );
312
313 } else if ( manuallyHandleCaret ) {
314 if ( fillerOnStart ) {
315 range.setStartAt( editable.getFirst().getNext(), CKEDITOR.POSITION_AFTER_START );
316 } else {
317 range.setEndAt( editable.getLast().getPrevious(), CKEDITOR.POSITION_BEFORE_END );
318 }
319 editable.editor.getSelection().selectRanges( [ range ] );
320 }
321 }
322 },
323
324 /**
325 * Creates a regular expression which will match the filler HTML in the text.
326 *
327 * @param {Boolean} [onEnd] Whether a regular expression should be created for the filler at the beginning or
328 * the end of the content.
329 * @returns {RegExp}
330 * @member CKEDITOR.plugins.widgetselection
331 * @private
332 */
333 createFillerRegex: function( onEnd ) {
334 var matcher = this.createFiller( onEnd ).getOuterHtml()
335 .replace( /style="[^"]*"/gi, 'style="[^"]*"' )
336 .replace( />[^<]*</gi, '>[^<]*<' );
337
338 return new RegExp( ( !onEnd ? '^' : '' ) + matcher + ( onEnd ? '$' : '' ) );
339 },
340
341 /**
342 * Adds an integration for the [Select All](http://ckeditor.com/addon/selectall) plugin to the given `editor`.
343 *
344 * @private
345 * @param {CKEDITOR.editor} editor
346 * @member CKEDITOR.plugins.widgetselection
347 */
348 addSelectAllIntegration: function( editor ) {
349 var widgetselection = this;
350
351 editor.editable().attachListener( editor, 'beforeCommandExec', function( evt ) {
352 var editable = editor.editable();
353
354 if ( evt.data.name == 'selectAll' && editable ) {
355 widgetselection.addFillers( editable );
356 }
357 }, null, null, 9999 );
358 }
359 };
360
361
362 function filterTempElements( el ) {
363 return el.getName && !el.hasAttribute( 'data-cke-temp' );
364 }
365
366} )();
diff --git a/app/assets/javascripts/init_ckeditor.coffee b/app/assets/javascripts/init_ckeditor.coffee new file mode 100644 index 0000000..5f653af --- /dev/null +++ b/app/assets/javascripts/init_ckeditor.coffee
@@ -0,0 +1,6 @@
1ready = ->
2 $('.ckeditor').each ->
3 CKEDITOR.replace $(this).attr('id')
4
5$(document).ready(ready)
6$(document).on('page:load', ready)
diff --git a/app/assets/javascripts/records.coffee b/app/assets/javascripts/records.coffee index 24f83d1..69cd471 100644 --- a/app/assets/javascripts/records.coffee +++ b/app/assets/javascripts/records.coffee
@@ -1,3 +1,9 @@
1# Place all the behaviors and hooks related to the matching controller here. 1# Place all the behaviors and hooks related to the matching controller here.
2# All this logic will automatically be available in application.js. 2# All this logic will automatically be available in application.js.
3# You can use CoffeeScript in this file: http://coffeescript.org/ 3# You can use CoffeeScript in this file: http://coffeescript.org/
4$(document).on "turbolinks:load", ->
5 $(".should-create-record-field input[type=checkbox]").change ->
6 if $(".should-create-record-field input[type=checkbox]").prop("checked")
7 $(".record-description-field").show()
8 else
9 $(".record-description-field").hide()
diff --git a/app/assets/stylesheets/admin.css.scss b/app/assets/stylesheets/admin.css.scss new file mode 100644 index 0000000..5235c4b --- /dev/null +++ b/app/assets/stylesheets/admin.css.scss
@@ -0,0 +1,4 @@
1/*
2 *= require normalize-rails
3 *= require_tree ./admin
4 */
diff --git a/app/assets/stylesheets/admin/dashboard.scss b/app/assets/stylesheets/admin/dashboard.scss new file mode 100644 index 0000000..c75b95a --- /dev/null +++ b/app/assets/stylesheets/admin/dashboard.scss
@@ -0,0 +1,3 @@
1// Place all the styles related to the Admin::Dashboard controller here.
2// They will automatically be included in application.css.
3// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/admin/layout.scss b/app/assets/stylesheets/admin/layout.scss new file mode 100644 index 0000000..811c2aa --- /dev/null +++ b/app/assets/stylesheets/admin/layout.scss
@@ -0,0 +1,190 @@
1html {
2 height: 100%;
3}
4
5body {
6 display: flex;
7 flex-direction: column;
8 min-height: 100%;
9}
10
11#banner {
12 background-color: #7bf;
13
14 a {
15 display: block;
16 text-decoration: none;
17 font-size: 2em;
18 margin: .25em 1em;
19
20 &, &:visited {
21 color: black;
22 }
23 }
24}
25
26#container {
27 display: flex;
28 flex-direction: row;
29 flex: 1;
30}
31
32#sidebar {
33 width: 12%;
34 background-color: #333;
35 margin: 0;
36 padding: 1em 0;
37
38 .major {
39 display: block;
40 color: white;
41
42 &.active {
43 background-color: #648;
44 }
45
46 &.inactive {
47 .minors {
48 display: none;
49 }
50 }
51
52 a {
53 display: block;
54 text-decoration: none;
55
56 &, &:visited {
57 color: #ddd;
58 }
59 }
60
61 .minors {
62 display: block;
63 background-color: #666;
64 }
65
66 .minor {
67 list-style-type: none;
68 font-size: small;
69 }
70
71 .major-link, .minors {
72 padding: .25em 1em;
73 }
74 }
75}
76
77#main {
78 width: 88%;
79 padding: 1em;
80 background-color: #eee;
81}
82
83#blog-form {
84 display: flex;
85
86 fieldset {
87 border: 0;
88 }
89
90 #content {
91 width: 77%;
92 }
93
94 #details {
95 width: 23%;
96 display: flex;
97 flex-direction: column;
98 padding-left: 0;
99 padding-right: 0;
100 }
101
102 .title-field {
103 label {
104 display: none;
105 }
106
107 input {
108 width: 100%;
109 font-size: 1.5em;
110 box-sizing: border-box;
111 }
112 }
113
114 .slug-field {
115 display: flex;
116 font-size: 0.75em;
117 margin-bottom: 1em;
118
119 input {
120 padding: 0;
121 border: 0;
122 color: #666;
123 width: 100%;
124 box-sizing: border-box;
125 background-color: #eee;
126 text-decoration: underline;
127
128 &:focus {
129 outline: 0;
130 }
131 }
132 }
133
134 .body-field {
135 label {
136 display: none;
137 }
138 }
139}
140
141#entries {
142 border-spacing: 0;
143 width: 100%;
144
145 th {
146 text-align: left;
147 padding: .25em;
148 font-weight: normal;
149 background-color: #35a;
150 color: #eee;
151 }
152
153 tr {
154 &.even {
155 background-color: #fff;
156 }
157
158 &.odd {
159 background-color: #edf;
160 }
161 }
162
163 td {
164 padding: .25em;
165 }
166}
167
168.details-module {
169 background-color: white;
170 border-radius: 5px;
171 padding: .5em;
172 margin-bottom: 1em;
173}
174
175.should-create-record-field {
176 label {
177 font-size: .75em;
178 }
179}
180
181.record-description-field {
182 display: none;
183 margin-top: 1em;
184
185 textarea {
186 width: 100%;
187 box-sizing: border-box;
188 border: 1px solid #ddd;
189 }
190}
diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/main/layout.scss index ebd4c87..8ddfdf0 100644 --- a/app/assets/stylesheets/main/layout.scss +++ b/app/assets/stylesheets/main/layout.scss
@@ -49,7 +49,7 @@ body#main-body {
49 49
50#content { 50#content {
51 box-sizing: border-box; 51 box-sizing: border-box;
52 padding-top: 1em; 52 padding: 1em 0;
53} 53}
54 54
55#flash { 55#flash {
diff --git a/app/controllers/admin/admin_controller.rb b/app/controllers/admin/admin_controller.rb new file mode 100644 index 0000000..400b02d --- /dev/null +++ b/app/controllers/admin/admin_controller.rb
@@ -0,0 +1,5 @@
1class Admin::AdminController < ApplicationController
2 before_action :authenticate_user!
3
4 layout "admin"
5end
diff --git a/app/controllers/admin/blogs_controller.rb b/app/controllers/admin/blogs_controller.rb new file mode 100644 index 0000000..fa46ab8 --- /dev/null +++ b/app/controllers/admin/blogs_controller.rb
@@ -0,0 +1,52 @@
1class Admin::BlogsController < Admin::AdminController
2 before_action :set_section
3
4 def index
5 @blogs = Blog.order(created_at: :desc)
6 end
7
8 def new
9 @blog = Blog.new
10 end
11
12 def create
13 @blog = Blog.new(blog_params)
14
15 if @blog.save
16 flash.notice = "Blog created successfully!"
17
18 render :edit
19 else
20 flash.alert = "Error creating blog."
21
22 render :new
23 end
24 end
25
26 def edit
27 @blog = Blog.find(params[:id])
28 end
29
30 def update
31 @blog = Blog.find(params[:id])
32
33 if @blog.update_attributes(blog_params)
34 flash.notice = "Blog updated successfully!"
35 else
36 flash.alert = "Error updating blog."
37 end
38
39 render :edit
40 end
41
42 private
43
44 def blog_params
45 params.require(:blog).permit(:title, :body, :slug, records_attributes: [:description, :_destroy])
46 end
47
48 def set_section
49 @section = "blogs"
50 end
51
52end
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb new file mode 100644 index 0000000..6d0e6bb --- /dev/null +++ b/app/controllers/admin/dashboard_controller.rb
@@ -0,0 +1,12 @@
1class Admin::DashboardController < Admin::AdminController
2 before_action :set_section
3
4 def index
5 end
6
7 private
8
9 def set_section
10 @section = "dashboard"
11 end
12end
diff --git a/app/controllers/entries_controller.rb b/app/controllers/entries_controller.rb index 366de53..14d779a 100644 --- a/app/controllers/entries_controller.rb +++ b/app/controllers/entries_controller.rb
@@ -1,26 +1,7 @@
1class EntriesController < ApplicationController 1class EntriesController < ApplicationController
2 before_action :authenticate_user!, only: [:edit, :update]
3 2
4 def show 3 def show
5 @entry = Entry.where(slug: params[:slug]).first 4 @entry = Entry.find_by_slug(params[:slug])
6 end 5 end
7 6
8 def edit
9 @entry = Entry.where(slug: params[:slug]).first
10 end
11
12 def update
13 @entry = Entry.where(slug: params[:slug]).first
14
15 if @entry.update_attributes(entry_params)
16 flash.notice = ""
17 end
18 end
19
20 private
21
22 def entry_params
23 params.require(:blog).permit(:title, :body, :slug)
24 end
25
26end 7end
diff --git a/app/helpers/admin/admin_helper.rb b/app/helpers/admin/admin_helper.rb new file mode 100644 index 0000000..97781a0 --- /dev/null +++ b/app/helpers/admin/admin_helper.rb
@@ -0,0 +1,11 @@
1module Admin::AdminHelper
2
3 def major_sidebar_attrs(section)
4 if section == @section
5 { class: "major active" }
6 else
7 { class: "major inactive" }
8 end
9 end
10
11end
diff --git a/app/helpers/admin/blogs_helper.rb b/app/helpers/admin/blogs_helper.rb new file mode 100644 index 0000000..8dbe542 --- /dev/null +++ b/app/helpers/admin/blogs_helper.rb
@@ -0,0 +1,2 @@
1module Admin::BlogsHelper
2end
diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb new file mode 100644 index 0000000..4052b7c --- /dev/null +++ b/app/helpers/admin/dashboard_helper.rb
@@ -0,0 +1,2 @@
1module Admin::DashboardHelper
2end
diff --git a/app/models/ckeditor/asset.rb b/app/models/ckeditor/asset.rb new file mode 100644 index 0000000..cf636ed --- /dev/null +++ b/app/models/ckeditor/asset.rb
@@ -0,0 +1,4 @@
1class Ckeditor::Asset < ActiveRecord::Base
2 include Ckeditor::Orm::ActiveRecord::AssetBase
3 include Ckeditor::Backend::Paperclip
4end
diff --git a/app/models/ckeditor/attachment_file.rb b/app/models/ckeditor/attachment_file.rb new file mode 100644 index 0000000..fe09132 --- /dev/null +++ b/app/models/ckeditor/attachment_file.rb
@@ -0,0 +1,13 @@
1class Ckeditor::AttachmentFile < Ckeditor::Asset
2 has_attached_file :data,
3 url: '/uploads/attachments/:id/:filename',
4 path: ':rails_root/public/uploads/attachments/:id/:filename'
5
6 validates_attachment_presence :data
7 validates_attachment_size :data, less_than: 100.megabytes
8 do_not_validate_attachment_file_type :data
9
10 def url_thumb
11 @url_thumb ||= Ckeditor::Utils.filethumb(filename)
12 end
13end
diff --git a/app/models/ckeditor/picture.rb b/app/models/ckeditor/picture.rb new file mode 100644 index 0000000..02aa9b2 --- /dev/null +++ b/app/models/ckeditor/picture.rb
@@ -0,0 +1,14 @@
1class Ckeditor::Picture < Ckeditor::Asset
2 has_attached_file :data,
3 url: '/uploads/pictures/:id/:style_:basename.:extension',
4 path: ':rails_root/public/uploads/pictures/:id/:style_:basename.:extension',
5 styles: { content: '800>', thumb: '118x100#' }
6
7 validates_attachment_presence :data
8 validates_attachment_size :data, less_than: 2.megabytes
9 validates_attachment_content_type :data, content_type: /\Aimage/
10
11 def url_content
12 url(:content)
13 end
14end
diff --git a/app/models/entry.rb b/app/models/entry.rb index 22b330a..87fd46d 100644 --- a/app/models/entry.rb +++ b/app/models/entry.rb
@@ -1,8 +1,10 @@
1class Entry < ApplicationRecord 1class Entry < ApplicationRecord
2 has_many :records, as: :recordable 2 has_many :records, as: :recordable, inverse_of: :recordable
3 3
4 validates :slug, presence: true, format: /\A[-a-z0-9]+\z/ 4 validates :slug, presence: true, format: /\A[-a-z0-9]+\z/
5 5
6 accepts_nested_attributes_for :records, allow_destroy: true
7
6 def path 8 def path
7 "/says/#{slug}" 9 "/says/#{slug}"
8 end 10 end
diff --git a/app/views/admin/blogs/_form.html.haml b/app/views/admin/blogs/_form.html.haml new file mode 100644 index 0000000..55a297a --- /dev/null +++ b/app/views/admin/blogs/_form.html.haml
@@ -0,0 +1,23 @@
1- if f.object.errors.any?
2 %ul#errors
3 - f.object.errors.full_messages.each do |error|
4 %li= error
5%fieldset#content
6 .title-field
7 = f.label :title
8 = f.text_field :title, placeholder: "Title"
9 .slug-field
10 = f.label :slug, "https://feffernoo.se/says/"
11 = f.text_field :slug, placeholder: "insert-slug-here"
12 .body-field
13 = f.label :body
14 = f.cktext_area :body
15%fieldset#details
16 .details-module
17 = f.fields_for :records, Record.new do |builder|
18 .should-create-record-field
19 = builder.check_box :_destroy, {checked: false}, "0", "1"
20 = builder.label :_destroy, "Create record?"
21 .record-description-field
22 = builder.text_area :description, placeholder: "record text"
23 .details-module= f.submit
diff --git a/app/views/admin/blogs/edit.html.haml b/app/views/admin/blogs/edit.html.haml new file mode 100644 index 0000000..3f4d412 --- /dev/null +++ b/app/views/admin/blogs/edit.html.haml
@@ -0,0 +1,2 @@
1= form_for @blog, url: admin_blog_url(@blog), html: { id: "blog-form" } do |f|
2 = render partial: "form", locals: { f: f }
diff --git a/app/views/admin/blogs/index.html.haml b/app/views/admin/blogs/index.html.haml new file mode 100644 index 0000000..448617a --- /dev/null +++ b/app/views/admin/blogs/index.html.haml
@@ -0,0 +1,10 @@
1%table#entries
2 %tr
3 %th Title
4 %th Date created
5 %th
6 - @blogs.each do |blog|
7 %tr{ class: cycle("even", "odd") }
8 %td= blog.title
9 %td= blog.created_at.strftime("%B %d, %Y, %l:%M%P")
10 %td= link_to "Edit", edit_admin_blog_url(blog)
diff --git a/app/views/admin/blogs/new.html.haml b/app/views/admin/blogs/new.html.haml new file mode 100644 index 0000000..914f27b --- /dev/null +++ b/app/views/admin/blogs/new.html.haml
@@ -0,0 +1,2 @@
1= form_for @blog, url: admin_blogs_url, html: { id: "blog-form" } do |f|
2 = render partial: "form", locals: { f: f }
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml new file mode 100644 index 0000000..eaac627 --- /dev/null +++ b/app/views/admin/dashboard/index.html.haml
@@ -0,0 +1 @@
Welcome to the the ubiquitous administration panel!
diff --git a/app/views/entries/edit.html.haml b/app/views/entries/edit.html.haml deleted file mode 100644 index 1872366..0000000 --- a/app/views/entries/edit.html.haml +++ /dev/null
@@ -1,11 +0,0 @@
1= form_for @entry, url: @entry.path do |f|
2 .field
3 = f.label :title
4 = f.text_field :title
5 .field
6 = f.label :body
7 = f.text_area :body
8 .field
9 = f.label :slug
10 = f.text_field :slug
11 = f.submit
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml new file mode 100644 index 0000000..a2c2d95 --- /dev/null +++ b/app/views/layouts/admin.html.haml
@@ -0,0 +1,27 @@
1!!! 5
2%html
3 %head
4 %title Thoughts - Admin
5 = csrf_meta_tags
6 = stylesheet_link_tag 'admin', media: 'all', 'data-turbolinks-track': 'reload'
7 = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
8 %body
9 #banner= link_to "Thoughts", root_url
10 #container
11 %ul#sidebar
12 %li{major_sidebar_attrs("dashboard")}
13 = link_to "Dashboard", admin_url, class: "major-link"
14 %li{major_sidebar_attrs("blogs")}
15 = link_to "Blogs", admin_blogs_url, class: "major-link"
16 %ul.minors
17 %li.minor= link_to "New blog", new_admin_blog_url
18 #main
19 - if flash[:alert]
20 #flash.flash-alert
21 %span.flash-tag ERROR:
22 = flash.alert
23 - if flash[:notice]
24 #flash.flash-notice
25 %span.flash-tag NOTICE:
26 = flash.notice
27 = yield
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 2a9e2eb..5148df4 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml
@@ -21,6 +21,7 @@
21 - if not user_signed_in? 21 - if not user_signed_in?
22 %li= link_to "Log in", new_user_session_path 22 %li= link_to "Log in", new_user_session_path
23 - if user_signed_in? 23 - if user_signed_in?
24 %li= link_to "Admin", admin_url
24 %li= link_to "Log out", destroy_user_session_path, method: :delete 25 %li= link_to "Log out", destroy_user_session_path, method: :delete
25 #main 26 #main
26 %header#banner= link_to "feffernoo.se", root_url 27 %header#banner= link_to "feffernoo.se", root_url
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 361538d..adbe234 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb
@@ -12,4 +12,5 @@ Rails.application.config.assets.paths << Rails.root.join('node_modules')
12# application.js, application.css, and all non-JS/CSS in the app/assets 12# application.js, application.css, and all non-JS/CSS in the app/assets
13# folder are already added. 13# folder are already added.
14# Rails.application.config.assets.precompile += %w( admin.js admin.css ) 14# Rails.application.config.assets.precompile += %w( admin.js admin.css )
15Rails.application.config.assets.precompile += %w( main userdata ) 15Rails.application.config.assets.precompile += %w( ckeditor/* )
16Rails.application.config.assets.precompile += %w( main userdata admin )
diff --git a/config/initializers/ckeditor.rb b/config/initializers/ckeditor.rb new file mode 100644 index 0000000..8f80a43 --- /dev/null +++ b/config/initializers/ckeditor.rb
@@ -0,0 +1,58 @@
1# Use this hook to configure ckeditor
2Ckeditor.setup do |config|
3 # ==> ORM configuration
4 # Load and configure the ORM. Supports :active_record (default), :mongo_mapper and
5 # :mongoid (bson_ext recommended) by default. Other ORMs may be
6 # available as additional gems.
7 require 'ckeditor/orm/active_record'
8
9 # Allowed image file types for upload.
10 # Set to nil or [] (empty array) for all file types
11 # By default: %w(jpg jpeg png gif tiff)
12 # config.image_file_types = %w(jpg jpeg png gif tiff)
13
14 # Allowed flash file types for upload.
15 # Set to nil or [] (empty array) for all file types
16 # By default: %w(jpg jpeg png gif tiff)
17 # config.flash_file_types = %w(swf)
18
19 # Allowed attachment file types for upload.
20 # Set to nil or [] (empty array) for all file types
21 # By default: %w(doc docx xls odt ods pdf rar zip tar tar.gz swf)
22 # config.attachment_file_types = %w(doc docx xls odt ods pdf rar zip tar tar.gz swf)
23
24 # Setup authorization to be run as a before filter
25 # By default: there is no authorization.
26 # config.authorize_with :cancan
27
28 # Override parent controller CKEditor inherits from
29 # By default: 'ApplicationController'
30 # config.parent_controller = 'MyController'
31
32 # Asset model classes
33 # config.picture_model { Ckeditor::Picture }
34 # config.attachment_file_model { Ckeditor::AttachmentFile }
35
36 # Paginate assets
37 # By default: 24
38 # config.default_per_page = 24
39
40 # Customize ckeditor assets path
41 # By default: nil
42 # config.asset_path = 'http://www.example.com/assets/ckeditor/'
43
44 # To reduce the asset precompilation time, you can limit plugins and/or languages to those you need:
45 # By default: nil (no limit)
46 # config.assets_languages = ['en', 'uk']
47 # config.assets_plugins = ['image', 'smiley']
48
49 # CKEditor CDN
50 # More info here http://cdn.ckeditor.com/
51 # By default: nil (CDN disabled)
52 # config.cdn_url = '//cdn.ckeditor.com/4.7.1/standard/ckeditor.js'
53
54 # JS config url
55 # Used when CKEditor CDN enabled
56 # By default: "ckeditor/config.js"
57 # config.js_config_url = 'ckeditor/config.js'
58end
diff --git a/config/routes.rb b/config/routes.rb index e409840..449cda1 100644 --- a/config/routes.rb +++ b/config/routes.rb
@@ -1,14 +1,14 @@
1Rails.application.routes.draw do 1Rails.application.routes.draw do
2 namespace :admin do
3 get '/', to: 'dashboard#index'
4
5 resources :blogs, except: [:show]
6 end
7
8 mount Ckeditor::Engine => '/ckeditor'
2 devise_for :users, controllers: { sessions: 'users/sessions' } 9 devise_for :users, controllers: { sessions: 'users/sessions' }
3 10
4 root "records#index" 11 root "records#index"
5 12
6 get 'says/:slug', to: 'entries#show' 13 get 'says/:slug', to: 'entries#show'
7 get 'says/:slug/edit', to: 'entries#edit'
8 put 'says/:slug', to: 'entries#update'
9 patch 'says/:slug', to: 'entries#update'
10
11 # get ':directory/:slug', to: 'entries#show', constraints: lambda { |request|
12 # Entry::DIRECTORIES.include? request.path_parameters['directory']
13 # }
14end 14end
diff --git a/db/migrate/20170629184901_create_ckeditor_assets.rb b/db/migrate/20170629184901_create_ckeditor_assets.rb new file mode 100644 index 0000000..da986fd --- /dev/null +++ b/db/migrate/20170629184901_create_ckeditor_assets.rb
@@ -0,0 +1,23 @@
1class CreateCkeditorAssets < ActiveRecord::Migration[5.1]
2 def self.up
3 create_table :ckeditor_assets do |t|
4 t.string :data_file_name, null: false
5 t.string :data_content_type
6 t.integer :data_file_size
7 t.string :data_fingerprint
8 t.string :type, limit: 30
9
10 # Uncomment it to save images dimensions, if your need it
11 t.integer :width
12 t.integer :height
13
14 t.timestamps null: false
15 end
16
17 add_index :ckeditor_assets, :type
18 end
19
20 def self.down
21 drop_table :ckeditor_assets
22 end
23end
diff --git a/db/schema.rb b/db/schema.rb index b690393..c7c82ac 100644 --- a/db/schema.rb +++ b/db/schema.rb
@@ -10,7 +10,20 @@
10# 10#
11# It's strongly recommended that you check this file into your version control system. 11# It's strongly recommended that you check this file into your version control system.
12 12
13ActiveRecord::Schema.define(version: 20170625151955) do 13ActiveRecord::Schema.define(version: 20170629184901) do
14
15 create_table "ckeditor_assets", force: :cascade do |t|
16 t.string "data_file_name", null: false
17 t.string "data_content_type"
18 t.integer "data_file_size"
19 t.string "data_fingerprint"
20 t.string "type", limit: 30
21 t.integer "width"
22 t.integer "height"
23 t.datetime "created_at", null: false
24 t.datetime "updated_at", null: false
25 t.index ["type"], name: "index_ckeditor_assets_on_type"
26 end
14 27
15 create_table "entries", force: :cascade do |t| 28 create_table "entries", force: :cascade do |t|
16 t.string "title" 29 t.string "title"
diff --git a/test/controllers/admin/blogs_controller_test.rb b/test/controllers/admin/blogs_controller_test.rb new file mode 100644 index 0000000..5b62eff --- /dev/null +++ b/test/controllers/admin/blogs_controller_test.rb
@@ -0,0 +1,19 @@
1require 'test_helper'
2
3class Admin::BlogsControllerTest < ActionDispatch::IntegrationTest
4 test "should get index" do
5 get admin_blogs_index_url
6 assert_response :success
7 end
8
9 test "should get new" do
10 get admin_blogs_new_url
11 assert_response :success
12 end
13
14 test "should get edit" do
15 get admin_blogs_edit_url
16 assert_response :success
17 end
18
19end
diff --git a/test/controllers/admin/dashboard_controller_test.rb b/test/controllers/admin/dashboard_controller_test.rb new file mode 100644 index 0000000..42bacb9 --- /dev/null +++ b/test/controllers/admin/dashboard_controller_test.rb
@@ -0,0 +1,9 @@
1require 'test_helper'
2
3class Admin::DashboardControllerTest < ActionDispatch::IntegrationTest
4 test "should get index" do
5 get admin_dashboard_index_url
6 assert_response :success
7 end
8
9end