integral 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +105 -0
- data/Rakefile +22 -0
- data/app/assets/images/integral/backend/data-unavailable.png +0 -0
- data/app/assets/images/integral/backend/logo.png +0 -0
- data/app/assets/images/integral/defaults/no_image_available.jpg +0 -0
- data/app/assets/images/integral/defaults/user_avatar.jpg +0 -0
- data/app/assets/images/integral/image-not-set.png +0 -0
- data/app/assets/images/integral/posts-hero-banner.jpg +0 -0
- data/app/assets/javascripts/ckeditor/loader.js.erb +6 -0
- data/app/assets/javascripts/ckeditor/my_config.js.erb +154 -0
- data/app/assets/javascripts/ckeditor/my_contents.sass +22 -0
- data/app/assets/javascripts/ckeditor/my_javascript.js +12 -0
- data/app/assets/javascripts/ckeditor/my_styles.js +5 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/az.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/bg.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ca.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/cs.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/da.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/de-ch.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/de.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/el.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/en-au.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/en.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/eo.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/es-mx.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/es.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/eu.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/fr.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/gl.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/hr.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/hu.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/it.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ja.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/km.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ko.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ku.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/mk.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/nb.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/nl.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/oc.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/pl.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/pt-br.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/pt.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ro.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ru.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/sk.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/sq.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/sv.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/tr.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ug.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/uk.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/vi.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/zh-cn.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/zh.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/autoembed/plugin.js +218 -0
- data/app/assets/javascripts/ckeditor/plugins/autogrow/plugin.js +232 -0
- data/app/assets/javascripts/ckeditor/plugins/autogrow/samples/autogrow.html +102 -0
- data/app/assets/javascripts/ckeditor/plugins/autolink/plugin.js +69 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/css/codemirror.min.css +1 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/autocomplete.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/autoformat.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/commentselectedrange.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/searchcode.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/uncommentselectedrange.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/beautify.min.js +2 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.addons.min.js +2 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.addons.search.min.js +1 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.min.js +6 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.bbcode.min.js +1 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.bbcodemixed.min.js +1 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.htmlmixed.min.js +2 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.javascript.min.js +1 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.php.min.js +3 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.twig.min.js +1 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/af.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ar.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/bg.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/bn.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/bs.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ca.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/cs.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/cy.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/da.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/de.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/el.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/en-au.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/en-ca.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/en-gb.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/en.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/eo.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/es.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/et.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/eu.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fa.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fi.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fo.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fr-ca.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fr.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/gl.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/gu.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/he.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/hi.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/hr.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/hu.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/is.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/it.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ja.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ka.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/km.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ko.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ku.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/lt.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/lv.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/mk.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/mn.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ms.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/nb.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/nl.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/no.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/pl.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/pt-br.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/pt.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ro.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ru.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sk.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sl.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sr-latn.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sr.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sv.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/th.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/tr.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ug.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/uk.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/vi.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/zh-cn.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/zh.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/plugin.js +1311 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/3024-day.css +41 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/3024-night.css +39 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/abcdef.css +32 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/ambiance-mobile.css +5 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/ambiance.css +74 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/base16-dark.css +38 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/base16-light.css +38 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/bespin.css +34 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/blackboard.css +32 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/cobalt.css +25 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/colorforth.css +33 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/dracula.css +40 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/duotone-dark.css +35 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/duotone-light.css +36 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/eclipse.css +23 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/elegant.css +13 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/erlang-dark.css +34 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/hopscotch.css +34 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/icecoder.css +43 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/isotope.css +34 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/lesser-dark.css +47 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/liquibyte.css +95 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/material.css +53 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/mbo.css +37 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/mdn-like.css +46 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/midnight.css +43 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/monokai.css +36 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/neat.css +12 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/neo.css +43 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/night.css +27 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/panda-syntax.css +85 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/paraiso-dark.css +38 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/paraiso-light.css +38 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/pastel-on-dark.css +52 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/railscasts.css +34 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/rubyblue.css +25 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/seti.css +44 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/solarized.css +168 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/the-matrix.css +30 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/tomorrow-night-bright.css +35 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/tomorrow-night-eighties.css +38 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/ttcn.css +64 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/twilight.css +32 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/vibrant-ink.css +34 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/xq-dark.css +53 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/xq-light.css +43 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/yeti.css +44 -0
- data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/zenburn.css +37 -0
- data/app/assets/javascripts/ckeditor/plugins/embed/icons/embed.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/embed/icons/hidpi/embed.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/embed/plugin.js +105 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/dialogs/embedbase.js +99 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/az.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/bg.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ca.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/cs.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/da.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/de-ch.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/de.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/en-au.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/en.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/eo.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/es-mx.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/es.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/eu.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/fr.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/gl.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/hr.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/hu.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/id.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/it.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ja.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ko.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ku.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/nb.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/nl.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/oc.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/pl.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/pt-br.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/pt.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ro.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ru.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/sk.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/sq.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/sv.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/tr.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ug.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/uk.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/zh-cn.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/zh.js +15 -0
- data/app/assets/javascripts/ckeditor/plugins/embedbase/plugin.js +652 -0
- data/app/assets/javascripts/ckeditor/plugins/embedjs/plugin.js +110 -0
- data/app/assets/javascripts/ckeditor/plugins/lineutils/dev/dnd.html +172 -0
- data/app/assets/javascripts/ckeditor/plugins/lineutils/dev/magicfinger.html +285 -0
- data/app/assets/javascripts/ckeditor/plugins/lineutils/plugin.js +1018 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/cs.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/da.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/de-ch.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/de.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/en.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/eo.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/eu.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/fr.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/gl.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/id.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/it.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/km.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/ko.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/ku.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/nb.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/nl.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/pl.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/pt-br.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/pt.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/ru.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/sv.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/tr.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/ug.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/uk.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/zh-cn.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/lang/zh.js +7 -0
- data/app/assets/javascripts/ckeditor/plugins/notification/plugin.js +923 -0
- data/app/assets/javascripts/ckeditor/plugins/notificationaggregator/plugin.js +548 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/dev/placeholder.html +60 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/dialogs/placeholder.js +49 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/icons/hidpi/placeholder.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/icons/placeholder.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/af.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ar.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/az.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/bg.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ca.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/cs.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/cy.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/da.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/de-ch.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/de.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/el.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/en-gb.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/en.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/eo.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/es-mx.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/es.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/et.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/eu.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/fa.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/fi.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/fr-ca.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/fr.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/gl.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/he.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/hr.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/hu.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/id.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/it.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ja.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/km.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ko.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ku.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/lv.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/nb.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/nl.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/no.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/oc.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/pl.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/pt-br.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/pt.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ru.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/si.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/sk.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/sl.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/sq.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/sv.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/th.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/tr.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/tt.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ug.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/uk.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/vi.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/zh-cn.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/zh.js +12 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/plugin.js +99 -0
- data/app/assets/javascripts/ckeditor/plugins/placeholder/samples/placeholder.html +75 -0
- data/app/assets/javascripts/ckeditor/plugins/strinsert/README.md +76 -0
- data/app/assets/javascripts/ckeditor/plugins/strinsert/bower.json +16 -0
- data/app/assets/javascripts/ckeditor/plugins/strinsert/plugin.js +107 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/contents.css +23 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/sample.jpg +0 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/contents.css +36 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js +51 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/icons/simplebox.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/plugin.js +114 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/dev/console.js +131 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/dev/nestedwidgets.html +134 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/dev/widgetstyles.html +144 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/images/handle.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/af.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/ar.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/az.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/bg.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/ca.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/cs.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/cy.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/da.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/de-ch.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/de.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/el.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/en-gb.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/en.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/eo.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/es-mx.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/es.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/eu.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/fa.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/fi.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/fr.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/gl.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/he.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/hr.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/hu.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/id.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/it.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/ja.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/km.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/ko.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/ku.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/lv.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/nb.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/nl.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/no.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/oc.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/pl.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/pt-br.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/pt.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/ru.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/sk.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/sl.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/sq.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/sv.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/tr.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/tt.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/ug.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/uk.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/vi.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/zh-cn.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/lang/zh.js +8 -0
- data/app/assets/javascripts/ckeditor/plugins/widget/plugin.js +4147 -0
- data/app/assets/javascripts/ckeditor/plugins/widgetselection/plugin.js +366 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/css/wordcount.css +3 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/ar.js +11 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/ca.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/da.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/de.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/el.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/en.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/es.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/fi.js +14 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/fr.js +11 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/he.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/hr.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/it.js +14 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/ja.js +14 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/jp.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/nl.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/no.js +10 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/pl.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/pt-br.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/pt.js +9 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/ru.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/sv.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/tr.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/zh-cn.js +13 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/plugin.js +407 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/samples/wordcount.html +61 -0
- data/app/assets/javascripts/ckeditor/plugins/wordcount/samples/wordcountWithMaxCount.html +110 -0
- data/app/assets/javascripts/integral/backend.js +268 -0
- data/app/assets/javascripts/integral/backend/extensions.js +1 -0
- data/app/assets/javascripts/integral/backend/support/Chart.bundle.min.js +10 -0
- data/app/assets/javascripts/integral/frontend.js +60 -0
- data/app/assets/javascripts/integral/support/character_counter.js +67 -0
- data/app/assets/javascripts/integral/support/chart_manager.coffee +145 -0
- data/app/assets/javascripts/integral/support/click_to_copy.coffee +28 -0
- data/app/assets/javascripts/integral/support/confirm_modal.coffee +58 -0
- data/app/assets/javascripts/integral/support/date_picker.coffee +83 -0
- data/app/assets/javascripts/integral/support/date_picker/picker.date.js +1435 -0
- data/app/assets/javascripts/integral/support/date_picker/picker.js +1132 -0
- data/app/assets/javascripts/integral/support/gallery.coffee +82 -0
- data/app/assets/javascripts/integral/support/google_analytics.coffee +23 -0
- data/app/assets/javascripts/integral/support/grid.coffee +92 -0
- data/app/assets/javascripts/integral/support/header_anchors.coffee +22 -0
- data/app/assets/javascripts/integral/support/image-preview.coffee +14 -0
- data/app/assets/javascripts/integral/support/image_selector.coffee +43 -0
- data/app/assets/javascripts/integral/support/image_uploader.coffee +59 -0
- data/app/assets/javascripts/integral/support/lib/html.sortable.js +691 -0
- data/app/assets/javascripts/integral/support/lib/jquery.are-you-sure.js +231 -0
- data/app/assets/javascripts/integral/support/lib/jquery_form.js +11 -0
- data/app/assets/javascripts/integral/support/lib/js.cookie.js +165 -0
- data/app/assets/javascripts/integral/support/lib/materialize-tags.js +758 -0
- data/app/assets/javascripts/integral/support/lib/materialize.clockpicker.js +699 -0
- data/app/assets/javascripts/integral/support/lib/swiper.js +7719 -0
- data/app/assets/javascripts/integral/support/lib/typeahead.js +2451 -0
- data/app/assets/javascripts/integral/support/lib/underscore.js +6 -0
- data/app/assets/javascripts/integral/support/list.coffee +71 -0
- data/app/assets/javascripts/integral/support/list_item.coffee +249 -0
- data/app/assets/javascripts/integral/support/notification_manager.coffee +17 -0
- data/app/assets/javascripts/integral/support/record_selector.coffee +161 -0
- data/app/assets/javascripts/integral/support/remote_form.coffee +87 -0
- data/app/assets/javascripts/integral/support/responsive_swiper.coffee +26 -0
- data/app/assets/javascripts/integral/support/scroll_to_top.coffee +17 -0
- data/app/assets/javascripts/integral/support/slug_generator.coffee +49 -0
- data/app/assets/stylesheets/errors.css +1834 -0
- data/app/assets/stylesheets/integral/backend.sass +66 -0
- data/app/assets/stylesheets/integral/backend/_foundation_settings.scss +867 -0
- data/app/assets/stylesheets/integral/backend/dashboard-layout.scss +491 -0
- data/app/assets/stylesheets/integral/backend/devise.sass +40 -0
- data/app/assets/stylesheets/integral/backend/foundation_and_overrides.scss +56 -0
- data/app/assets/stylesheets/integral/backend/materialize-tags.sass +70 -0
- data/app/assets/stylesheets/integral/backend/notifications.sass +43 -0
- data/app/assets/stylesheets/integral/backend/overrides.sass +1 -0
- data/app/assets/stylesheets/integral/backend/shared.sass +770 -0
- data/app/assets/stylesheets/integral/emails.scss +19 -0
- data/app/assets/stylesheets/integral/emails/colors.sass +3 -0
- data/app/assets/stylesheets/integral/frontend.sass +26 -0
- data/app/assets/stylesheets/integral/frontend/_foundation_settings.scss +863 -0
- data/app/assets/stylesheets/integral/frontend/blog.sass +263 -0
- data/app/assets/stylesheets/integral/frontend/demo.sass +158 -0
- data/app/assets/stylesheets/integral/frontend/foundation_and_overrides.scss +56 -0
- data/app/assets/stylesheets/integral/frontend/layout.sass +66 -0
- data/app/assets/stylesheets/integral/frontend/overrides.sass +1 -0
- data/app/assets/stylesheets/integral/frontend/share_modal.sass +25 -0
- data/app/assets/stylesheets/integral/frontend/shared.sass +73 -0
- data/app/assets/stylesheets/integral/support/date-picker.scss +16 -0
- data/app/assets/stylesheets/integral/support/date_picker/_default.date.scss +452 -0
- data/app/assets/stylesheets/integral/support/date_picker/_default.scss +201 -0
- data/app/assets/stylesheets/integral/support/date_picker/_default.time.scss +125 -0
- data/app/assets/stylesheets/integral/support/date_picker/materialize_clockpicker.sass +220 -0
- data/app/assets/stylesheets/integral/support/gallery.sass +54 -0
- data/app/assets/stylesheets/integral/support/media-query-indicator.sass +25 -0
- data/app/assets/stylesheets/integral/support/mixins.sass +29 -0
- data/app/assets/stylesheets/integral/support/scroll-to-top.sass +19 -0
- data/app/assets/stylesheets/integral/support/swiper.css +618 -0
- data/app/assets/stylesheets/integral/wysiwyg.sass +13 -0
- data/app/controllers/integral/application_controller.rb +130 -0
- data/app/controllers/integral/backend/activities_controller.rb +55 -0
- data/app/controllers/integral/backend/base_controller.rb +123 -0
- data/app/controllers/integral/backend/images_controller.rb +120 -0
- data/app/controllers/integral/backend/lists_controller.rb +138 -0
- data/app/controllers/integral/backend/pages_controller.rb +142 -0
- data/app/controllers/integral/backend/posts_controller.rb +132 -0
- data/app/controllers/integral/backend/settings_controller.rb +53 -0
- data/app/controllers/integral/backend/static_pages_controller.rb +10 -0
- data/app/controllers/integral/backend/users_controller.rb +130 -0
- data/app/controllers/integral/blog_controller.rb +19 -0
- data/app/controllers/integral/contact_controller.rb +36 -0
- data/app/controllers/integral/pages_controller.rb +45 -0
- data/app/controllers/integral/posts_controller.rb +71 -0
- data/app/controllers/integral/tags_controller.rb +38 -0
- data/app/decorators/integral/image_decorator.rb +16 -0
- data/app/decorators/integral/list_decorator.rb +21 -0
- data/app/decorators/integral/page_decorator.rb +26 -0
- data/app/decorators/integral/post_decorator.rb +63 -0
- data/app/decorators/integral/user_decorator.rb +21 -0
- data/app/decorators/integral/version_decorator.rb +57 -0
- data/app/helpers/integral/application_helper.rb +52 -0
- data/app/helpers/integral/backend/base_helper.rb +72 -0
- data/app/helpers/integral/blog_helper.rb +26 -0
- data/app/helpers/integral/gallery_helper.rb +36 -0
- data/app/helpers/integral/mail_helper.rb +16 -0
- data/app/helpers/integral/social_helper.rb +123 -0
- data/app/helpers/integral/support_helper.rb +60 -0
- data/app/jobs/integral/application_job.rb +10 -0
- data/app/jobs/integral/newsletter_signup_job.rb +28 -0
- data/app/mailers/integral/contact_mailer.rb +56 -0
- data/app/models/ckeditor/asset.rb +8 -0
- data/app/models/ckeditor/attachment_file.rb +9 -0
- data/app/models/ckeditor/picture.rb +21 -0
- data/app/models/integral/application_record.rb +6 -0
- data/app/models/integral/basic.rb +11 -0
- data/app/models/integral/enquiry.rb +46 -0
- data/app/models/integral/image.rb +67 -0
- data/app/models/integral/image_version.rb +8 -0
- data/app/models/integral/link.rb +11 -0
- data/app/models/integral/list.rb +69 -0
- data/app/models/integral/list_item.rb +89 -0
- data/app/models/integral/list_version.rb +8 -0
- data/app/models/integral/newsletter_signup.rb +22 -0
- data/app/models/integral/object.rb +30 -0
- data/app/models/integral/page.rb +153 -0
- data/app/models/integral/page_version.rb +8 -0
- data/app/models/integral/post.rb +149 -0
- data/app/models/integral/post_version.rb +8 -0
- data/app/models/integral/post_viewing.rb +19 -0
- data/app/models/integral/role.rb +14 -0
- data/app/models/integral/role_assignment.rb +7 -0
- data/app/models/integral/settings.rb +5 -0
- data/app/models/integral/user.rb +59 -0
- data/app/models/integral/user_version.rb +8 -0
- data/app/models/integral/version.rb +31 -0
- data/app/models/paper_trail/version.rb +9 -0
- data/app/policies/integral/base_policy.rb +50 -0
- data/app/policies/integral/image_policy.rb +14 -0
- data/app/policies/integral/list_policy.rb +9 -0
- data/app/policies/integral/page_policy.rb +45 -0
- data/app/policies/integral/post_policy.rb +9 -0
- data/app/policies/integral/settings_policy.rb +17 -0
- data/app/policies/integral/user_policy.rb +17 -0
- data/app/policies/integral/version_policy.rb +19 -0
- data/app/uploaders/ckeditor_attachment_file_uploader.rb +34 -0
- data/app/uploaders/ckeditor_picture_uploader.rb +55 -0
- data/app/uploaders/integral/avatar_uploader.rb +9 -0
- data/app/uploaders/integral/image_uploader.rb +114 -0
- data/app/views/devise/invitations/edit.haml +17 -0
- data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/mailer/invitation_instructions.html.erb +13 -0
- data/app/views/devise/mailer/invitation_instructions.text.erb +12 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/devise/passwords/edit.haml +17 -0
- data/app/views/devise/passwords/new.haml +12 -0
- data/app/views/devise/sessions/new.haml +18 -0
- data/app/views/devise/shared/_links.html.erb +25 -0
- data/app/views/integral/backend/activities/_grid.haml +23 -0
- data/app/views/integral/backend/activities/index.haml +15 -0
- data/app/views/integral/backend/activities/shared/_grid.haml +20 -0
- data/app/views/integral/backend/activities/shared/_listing.haml +14 -0
- data/app/views/integral/backend/activities/shared/_log.haml +56 -0
- data/app/views/integral/backend/activities/show.haml +18 -0
- data/app/views/integral/backend/images/_create_modal.haml +17 -0
- data/app/views/integral/backend/images/_form.haml +41 -0
- data/app/views/integral/backend/images/_grid.haml +16 -0
- data/app/views/integral/backend/images/edit.haml +3 -0
- data/app/views/integral/backend/images/index.haml +18 -0
- data/app/views/integral/backend/images/new.haml +1 -0
- data/app/views/integral/backend/lists/_child_fields.haml +26 -0
- data/app/views/integral/backend/lists/_form.haml +25 -0
- data/app/views/integral/backend/lists/_grid.haml +16 -0
- data/app/views/integral/backend/lists/_item_container.haml +26 -0
- data/app/views/integral/backend/lists/_item_modal.haml +46 -0
- data/app/views/integral/backend/lists/_list_item_fields.haml +26 -0
- data/app/views/integral/backend/lists/_manager.haml +14 -0
- data/app/views/integral/backend/lists/edit.haml +2 -0
- data/app/views/integral/backend/lists/index.haml +18 -0
- data/app/views/integral/backend/lists/new.haml +1 -0
- data/app/views/integral/backend/lists/show.haml +26 -0
- data/app/views/integral/backend/pages/_form.haml +52 -0
- data/app/views/integral/backend/pages/_grid.haml +21 -0
- data/app/views/integral/backend/pages/activities.haml +2 -0
- data/app/views/integral/backend/pages/activity.haml +1 -0
- data/app/views/integral/backend/pages/edit.html.haml +15 -0
- data/app/views/integral/backend/pages/index.haml +22 -0
- data/app/views/integral/backend/pages/new.haml +2 -0
- data/app/views/integral/backend/posts/_form.haml +57 -0
- data/app/views/integral/backend/posts/_grid.haml +25 -0
- data/app/views/integral/backend/posts/activities.haml +3 -0
- data/app/views/integral/backend/posts/activity.haml +1 -0
- data/app/views/integral/backend/posts/edit.haml +16 -0
- data/app/views/integral/backend/posts/index.haml +20 -0
- data/app/views/integral/backend/posts/new.haml +2 -0
- data/app/views/integral/backend/settings/_input.haml +4 -0
- data/app/views/integral/backend/settings/_section.haml +11 -0
- data/app/views/integral/backend/settings/index.haml +10 -0
- data/app/views/integral/backend/settings/sections/_general.haml +12 -0
- data/app/views/integral/backend/settings/sections/_lists.haml +4 -0
- data/app/views/integral/backend/settings/sections/_seo.haml +8 -0
- data/app/views/integral/backend/settings/sections/_social.haml +16 -0
- data/app/views/integral/backend/shared/_breadcrumbs.haml +9 -0
- data/app/views/integral/backend/shared/_empty_grid.haml +7 -0
- data/app/views/integral/backend/shared/_grid.haml +18 -0
- data/app/views/integral/backend/shared/_image_preview.haml +23 -0
- data/app/views/integral/backend/shared/_image_selector.haml +4 -0
- data/app/views/integral/backend/shared/_pagination.haml +1 -0
- data/app/views/integral/backend/shared/cards/_at_a_glance.haml +5 -0
- data/app/views/integral/backend/shared/cards/_welcome.haml +29 -0
- data/app/views/integral/backend/shared/graphs/_donut.haml +5 -0
- data/app/views/integral/backend/shared/graphs/_line.haml +5 -0
- data/app/views/integral/backend/shared/graphs/_no_data_available.haml +2 -0
- data/app/views/integral/backend/shared/record_selector/_collection.haml +13 -0
- data/app/views/integral/backend/shared/record_selector/_modal.haml +42 -0
- data/app/views/integral/backend/shared/record_selector/_record.haml +6 -0
- data/app/views/integral/backend/static_pages/_card.haml +24 -0
- data/app/views/integral/backend/static_pages/dashboard.haml +20 -0
- data/app/views/integral/backend/users/_form.haml +58 -0
- data/app/views/integral/backend/users/_grid.haml +19 -0
- data/app/views/integral/backend/users/edit.haml +3 -0
- data/app/views/integral/backend/users/index.haml +18 -0
- data/app/views/integral/backend/users/new.haml +2 -0
- data/app/views/integral/backend/users/show.haml +36 -0
- data/app/views/integral/contact_mailer/auto_reply.html.haml +16 -0
- data/app/views/integral/contact_mailer/auto_reply.text.erb +15 -0
- data/app/views/integral/contact_mailer/forward_enquiry.haml +12 -0
- data/app/views/integral/contact_mailer/forward_enquiry.text.erb +9 -0
- data/app/views/integral/pages/_demo.haml +188 -0
- data/app/views/integral/pages/templates/default.haml +6 -0
- data/app/views/integral/posts/_item.haml +16 -0
- data/app/views/integral/posts/index.haml +6 -0
- data/app/views/integral/posts/templates/default.haml +38 -0
- data/app/views/integral/shared/_blog_layout.haml +15 -0
- data/app/views/integral/shared/_blog_sidebar.haml +49 -0
- data/app/views/integral/shared/_breadcrumbs.haml +5 -0
- data/app/views/integral/shared/_media_query_indicator.haml +2 -0
- data/app/views/integral/shared/_scroll_to_top.haml +3 -0
- data/app/views/integral/shared/_share_modal.haml +20 -0
- data/app/views/integral/shared/_social_list_items.haml +41 -0
- data/app/views/integral/shared/_swiper_layout.haml +20 -0
- data/app/views/integral/shared/gallery/_placeholder.haml +7 -0
- data/app/views/integral/shared/gallery/_slide.haml +5 -0
- data/app/views/integral/shared/gallery/_thumb_slide.haml +2 -0
- data/app/views/integral/shared/gallery/gallery.haml +7 -0
- data/app/views/integral/tags/index.haml +6 -0
- data/app/views/integral/tags/show.haml +6 -0
- data/app/views/layouts/error.haml +12 -0
- data/app/views/layouts/integral/backend.html.haml +93 -0
- data/app/views/layouts/integral/backend/_create_dropdown.haml +30 -0
- data/app/views/layouts/integral/backend/_head.html.haml +17 -0
- data/app/views/layouts/integral/backend/_main_menu_items.haml +91 -0
- data/app/views/layouts/integral/frontend.html.haml +36 -0
- data/app/views/layouts/integral/frontend/_admin_bar.haml +25 -0
- data/app/views/layouts/integral/frontend/_footer.haml +23 -0
- data/app/views/layouts/integral/frontend/_header.haml +12 -0
- data/app/views/layouts/integral/login.haml +8 -0
- data/app/views/layouts/integral/mailer.html.inky-haml +17 -0
- data/app/views/layouts/integral/mailer/_footer.html.inky-haml +11 -0
- data/app/views/layouts/integral/mailer/_header.html.inky-haml +7 -0
- data/config/initializers/acts_as_taggable_on.rb +6 -0
- data/config/initializers/ckeditor.rb +51 -0
- data/config/initializers/client_side_validations.rb +20 -0
- data/config/initializers/compression.rb +46 -0
- data/config/initializers/devise.rb +308 -0
- data/config/initializers/extensions/ckeditor.rb +31 -0
- data/config/initializers/friendly_id.rb +88 -0
- data/config/initializers/gaffe.rb +3 -0
- data/config/initializers/simple_form.rb +169 -0
- data/config/initializers/simple_form/length_component.rb +36 -0
- data/config/locales/devise.en.yml +60 -0
- data/config/locales/devise_invitable.en.yml +31 -0
- data/config/locales/en.yml +477 -0
- data/config/locales/simple_form.en.yml +31 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20160129021044_devise_create_integral_users.rb +42 -0
- data/db/migrate/20160130051535_add_name_and_avatar_to_users.rb +6 -0
- data/db/migrate/20160201003936_add_roles.rb +13 -0
- data/db/migrate/20160302071241_create_integral_images.rb +13 -0
- data/db/migrate/20160311092506_create_ckeditor_assets.rb +26 -0
- data/db/migrate/20160318004136_create_integral_pages.rb +12 -0
- data/db/migrate/20160513033619_create_integral_posts.rb +12 -0
- data/db/migrate/20160514063048_add_slug_column_to_post.rb +6 -0
- data/db/migrate/20160516084702_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb +31 -0
- data/db/migrate/20160516084703_add_missing_unique_indices.acts_as_taggable_on_engine.rb +20 -0
- data/db/migrate/20160516084704_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb +15 -0
- data/db/migrate/20160516084705_add_missing_taggable_index.acts_as_taggable_on_engine.rb +10 -0
- data/db/migrate/20160516084706_change_collation_for_tag_names.acts_as_taggable_on_engine.rb +10 -0
- data/db/migrate/20160519005736_add_image_to_posts.rb +5 -0
- data/db/migrate/20160519053329_create_friendly_id_slugs.rb +15 -0
- data/db/migrate/20160610021858_add_view_count_to_posts.rb +5 -0
- data/db/migrate/20160613035508_add_drafting_to_posts.rb +6 -0
- data/db/migrate/20160628050006_create_integral_post_viewings.rb +10 -0
- data/db/migrate/20160718011218_add_locale_to_integral_users.rb +5 -0
- data/db/migrate/20160722100113_devise_invitable_add_to_integral_users.rb +23 -0
- data/db/migrate/20160809115906_add_drafting_to_pages.rb +5 -0
- data/db/migrate/20160909124940_add_deleted_at_to_objects.rb +15 -0
- data/db/migrate/20161014175823_create_lists.rb +31 -0
- data/db/migrate/20161102094859_create_settings.rb +18 -0
- data/db/migrate/20170215012231_add_templates_to_pages.rb +5 -0
- data/db/migrate/20170425091312_add_image_processing_fields.rb +7 -0
- data/db/migrate/20170516033523_add_timestamps_to_lists.rb +6 -0
- data/db/migrate/20170608131624_add_file_size_to_images.rb +5 -0
- data/db/migrate/20170801182434_add_parent_to_pages.rb +5 -0
- data/db/migrate/20170922201940_add_image_id_to_posts.rb +9 -0
- data/db/migrate/20171008193414_create_post_versions.rb +21 -0
- data/db/migrate/20171030215146_create_page_versions.rb +20 -0
- data/db/migrate/20171030215154_create_user_versions.rb +20 -0
- data/db/migrate/20171030215202_create_list_versions.rb +20 -0
- data/db/migrate/20171123155010_create_integral_enquiries.rb +13 -0
- data/db/migrate/20171127141132_add_deleted_at_to_lists.rb +6 -0
- data/db/migrate/20171230213848_add_image_to_pages.rb +6 -0
- data/db/migrate/20180202010010_add_lock_version_to_integral_objects.rb +9 -0
- data/db/migrate/20180202010020_create_image_versions.rb +20 -0
- data/db/migrate/20180207174914_add_fields_to_lists.rb +7 -0
- data/db/migrate/20180223232512_create_integral_newsletter_signups.rb +11 -0
- data/db/migrate/20180306204912_add_admin_to_integral_users.rb +5 -0
- data/db/migrate/20180326224500_add_processed_to_enquiries_and_newsletters.rb +6 -0
- data/db/migrate/20180509101917_add_context_to_enquiries.rb +6 -0
- data/db/migrate/20180920030236_update_posts_and_pages_null.rb +6 -0
- data/db/migrate/20181011234446_add_preview_image_to_posts.rb +6 -0
- data/db/seeds.rb +53 -0
- data/lib/generators/integral/install_generator.rb +19 -0
- data/lib/generators/templates/app.yml +24 -0
- data/lib/generators/templates/carrierwave.rb +35 -0
- data/lib/generators/templates/carrierwave_backgrounder.rb +11 -0
- data/lib/generators/templates/integral.rb +129 -0
- data/lib/generators/templates/sitemap.rb +41 -0
- data/lib/integral.rb +155 -0
- data/lib/integral/acts_as_listable.rb +45 -0
- data/lib/integral/button_link_renderer.rb +54 -0
- data/lib/integral/chart_renderer/base.rb +80 -0
- data/lib/integral/chart_renderer/donut.rb +21 -0
- data/lib/integral/chart_renderer/line.rb +24 -0
- data/lib/integral/content_renderer.rb +78 -0
- data/lib/integral/engine.rb +124 -0
- data/lib/integral/foundation_builder.rb +33 -0
- data/lib/integral/google_tag_manager.rb +33 -0
- data/lib/integral/grids/activities_grid.rb +37 -0
- data/lib/integral/grids/images_grid.rb +21 -0
- data/lib/integral/grids/lists_grid.rb +21 -0
- data/lib/integral/grids/pages_grid.rb +27 -0
- data/lib/integral/grids/posts_grid.rb +32 -0
- data/lib/integral/grids/users_grid.rb +22 -0
- data/lib/integral/list_item_renderer.rb +204 -0
- data/lib/integral/list_renderer.rb +91 -0
- data/lib/integral/middleware/page_router.rb +84 -0
- data/lib/integral/partial_list_item_renderer.rb +40 -0
- data/lib/integral/router.rb +106 -0
- data/lib/integral/slack_bot.rb +45 -0
- data/lib/integral/swiper_list_renderer.rb +30 -0
- data/lib/integral/version.rb +5 -0
- data/lib/integral/widgets/recent_posts.rb +41 -0
- data/lib/integral/widgets/swiper_list.rb +40 -0
- data/lib/tasks/integral_tasks.rake +57 -0
- data/lib/templates/erb/scaffold/_form.html.erb +13 -0
- data/spec/factories.rb +114 -0
- data/spec/support/image.jpg +0 -0
- metadata +1734 -0
@@ -0,0 +1,4147 @@
|
|
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) —
|
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) —
|
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 — 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 — 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 — 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) —
|
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 && !editableDefinition.disallowedContent )
|
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
|
+
filter = editableDefinition.allowedContent ? new CKEDITOR.filter( editableDefinition.allowedContent ) : this.editor.filter.clone();
|
2110
|
+
|
2111
|
+
editables[ editableName ] = filter;
|
2112
|
+
|
2113
|
+
if ( editableDefinition.disallowedContent ) {
|
2114
|
+
filter.disallow( editableDefinition.disallowedContent );
|
2115
|
+
}
|
2116
|
+
}
|
2117
|
+
|
2118
|
+
return filter;
|
2119
|
+
}
|
2120
|
+
|
2121
|
+
// Creates an iterator function which when executed on all
|
2122
|
+
// elements in DOM tree will gather elements that should be wrapped
|
2123
|
+
// and initialized as widgets.
|
2124
|
+
function createUpcastIterator( widgetsRepo ) {
|
2125
|
+
var toBeWrapped = [],
|
2126
|
+
upcasts = widgetsRepo._.upcasts,
|
2127
|
+
upcastCallbacks = widgetsRepo._.upcastCallbacks;
|
2128
|
+
|
2129
|
+
return {
|
2130
|
+
toBeWrapped: toBeWrapped,
|
2131
|
+
|
2132
|
+
iterator: function( element ) {
|
2133
|
+
var upcast, upcasted,
|
2134
|
+
data,
|
2135
|
+
i,
|
2136
|
+
upcastsLength,
|
2137
|
+
upcastCallbacksLength;
|
2138
|
+
|
2139
|
+
// Wrapper found - find widget element, add it to be
|
2140
|
+
// cleaned up (unwrapped) and wrapped and stop iterating in this branch.
|
2141
|
+
if ( 'data-cke-widget-wrapper' in element.attributes ) {
|
2142
|
+
element = element.getFirst( Widget.isParserWidgetElement );
|
2143
|
+
|
2144
|
+
if ( element )
|
2145
|
+
toBeWrapped.push( [ element ] );
|
2146
|
+
|
2147
|
+
// Do not iterate over descendants.
|
2148
|
+
return false;
|
2149
|
+
}
|
2150
|
+
// Widget element found - add it to be cleaned up (just in case)
|
2151
|
+
// and wrapped and stop iterating in this branch.
|
2152
|
+
else if ( 'data-widget' in element.attributes ) {
|
2153
|
+
toBeWrapped.push( [ element ] );
|
2154
|
+
|
2155
|
+
// Do not iterate over descendants.
|
2156
|
+
return false;
|
2157
|
+
}
|
2158
|
+
else if ( ( upcastsLength = upcasts.length ) ) {
|
2159
|
+
// Ignore elements with data-cke-widget-upcasted to avoid multiple upcasts (http://dev.ckeditor.com/ticket/11533).
|
2160
|
+
// Do not iterate over descendants.
|
2161
|
+
if ( element.attributes[ 'data-cke-widget-upcasted' ] )
|
2162
|
+
return false;
|
2163
|
+
|
2164
|
+
// Check element with upcast callbacks first.
|
2165
|
+
// If any of them return false abort upcasting.
|
2166
|
+
for ( i = 0, upcastCallbacksLength = upcastCallbacks.length; i < upcastCallbacksLength; ++i ) {
|
2167
|
+
if ( upcastCallbacks[ i ]( element ) === false )
|
2168
|
+
return;
|
2169
|
+
// Return nothing in order to continue iterating over ascendants.
|
2170
|
+
// See http://dev.ckeditor.com/ticket/11186#comment:6
|
2171
|
+
}
|
2172
|
+
|
2173
|
+
for ( i = 0; i < upcastsLength; ++i ) {
|
2174
|
+
upcast = upcasts[ i ];
|
2175
|
+
data = {};
|
2176
|
+
|
2177
|
+
if ( ( upcasted = upcast[ 0 ]( element, data ) ) ) {
|
2178
|
+
// If upcast function returned element, upcast this one.
|
2179
|
+
// It can be e.g. a new element wrapping the original one.
|
2180
|
+
if ( upcasted instanceof CKEDITOR.htmlParser.element )
|
2181
|
+
element = upcasted;
|
2182
|
+
|
2183
|
+
// Set initial data attr with data from upcast method.
|
2184
|
+
element.attributes[ 'data-cke-widget-data' ] = encodeURIComponent( JSON.stringify( data ) );
|
2185
|
+
element.attributes[ 'data-cke-widget-upcasted' ] = 1;
|
2186
|
+
|
2187
|
+
toBeWrapped.push( [ element, upcast[ 1 ] ] );
|
2188
|
+
|
2189
|
+
// Do not iterate over descendants.
|
2190
|
+
return false;
|
2191
|
+
}
|
2192
|
+
}
|
2193
|
+
}
|
2194
|
+
}
|
2195
|
+
};
|
2196
|
+
}
|
2197
|
+
|
2198
|
+
// Finds a first parent that matches query.
|
2199
|
+
//
|
2200
|
+
// @param {CKEDITOR.dom.element} element
|
2201
|
+
// @param {Function} query
|
2202
|
+
function findParent( element, query ) {
|
2203
|
+
var parent = element;
|
2204
|
+
|
2205
|
+
while ( ( parent = parent.getParent() ) ) {
|
2206
|
+
if ( query( parent ) )
|
2207
|
+
return true;
|
2208
|
+
}
|
2209
|
+
return false;
|
2210
|
+
}
|
2211
|
+
|
2212
|
+
function getWrapperAttributes( inlineWidget, name ) {
|
2213
|
+
return {
|
2214
|
+
// tabindex="-1" means that it can receive focus by code.
|
2215
|
+
tabindex: -1,
|
2216
|
+
contenteditable: 'false',
|
2217
|
+
'data-cke-widget-wrapper': 1,
|
2218
|
+
'data-cke-filter': 'off',
|
2219
|
+
// Class cke_widget_new marks widgets which haven't been initialized yet.
|
2220
|
+
'class': 'cke_widget_wrapper cke_widget_new cke_widget_' +
|
2221
|
+
( inlineWidget ? 'inline' : 'block' ) +
|
2222
|
+
( name ? ' cke_widget_' + name : '' )
|
2223
|
+
};
|
2224
|
+
}
|
2225
|
+
|
2226
|
+
// Inserts element at given index.
|
2227
|
+
// It will check DTD and split ancestor elements up to the first
|
2228
|
+
// that can contain this element.
|
2229
|
+
//
|
2230
|
+
// @param {CKEDITOR.htmlParser.element} parent
|
2231
|
+
// @param {Number} index
|
2232
|
+
// @param {CKEDITOR.htmlParser.element} element
|
2233
|
+
function insertElement( parent, index, element ) {
|
2234
|
+
// Do not split doc fragment...
|
2235
|
+
if ( parent.type == CKEDITOR.NODE_ELEMENT ) {
|
2236
|
+
var parentAllows = CKEDITOR.dtd[ parent.name ];
|
2237
|
+
// Parent element is known (included in DTD) and cannot contain
|
2238
|
+
// this element.
|
2239
|
+
if ( parentAllows && !parentAllows[ element.name ] ) {
|
2240
|
+
var parent2 = parent.split( index ),
|
2241
|
+
parentParent = parent.parent;
|
2242
|
+
|
2243
|
+
// Element will now be inserted at right parent's index.
|
2244
|
+
index = parent2.getIndex();
|
2245
|
+
|
2246
|
+
// If left part of split is empty - remove it.
|
2247
|
+
if ( !parent.children.length ) {
|
2248
|
+
index -= 1;
|
2249
|
+
parent.remove();
|
2250
|
+
}
|
2251
|
+
|
2252
|
+
// If right part of split is empty - remove it.
|
2253
|
+
if ( !parent2.children.length )
|
2254
|
+
parent2.remove();
|
2255
|
+
|
2256
|
+
// Try inserting as grandpas' children.
|
2257
|
+
return insertElement( parentParent, index, element );
|
2258
|
+
}
|
2259
|
+
}
|
2260
|
+
|
2261
|
+
// Finally we can add this element.
|
2262
|
+
parent.add( element, index );
|
2263
|
+
}
|
2264
|
+
|
2265
|
+
// Checks whether for the given widget definition and element widget should be created in inline or block mode.
|
2266
|
+
//
|
2267
|
+
// See also: {@link CKEDITOR.plugins.widget.definition#inline} and {@link CKEDITOR.plugins.widget#element}.
|
2268
|
+
//
|
2269
|
+
// @param {CKEDITOR.plugins.widget.definition} widgetDef The widget definition.
|
2270
|
+
// @param {String} elementName The name of the widget element.
|
2271
|
+
// @returns {Boolean}
|
2272
|
+
function isWidgetInline( widgetDef, elementName ) {
|
2273
|
+
return typeof widgetDef.inline == 'boolean' ? widgetDef.inline : !!CKEDITOR.dtd.$inline[ elementName ];
|
2274
|
+
}
|
2275
|
+
|
2276
|
+
// @param {CKEDITOR.dom.element}
|
2277
|
+
// @returns {Boolean}
|
2278
|
+
function isDomTemp( element ) {
|
2279
|
+
return element.hasAttribute( 'data-cke-temp' );
|
2280
|
+
}
|
2281
|
+
|
2282
|
+
function onEditableKey( widget, keyCode ) {
|
2283
|
+
var focusedEditable = widget.focusedEditable,
|
2284
|
+
range;
|
2285
|
+
|
2286
|
+
// CTRL+A.
|
2287
|
+
if ( keyCode == CKEDITOR.CTRL + 65 ) {
|
2288
|
+
var bogus = focusedEditable.getBogus();
|
2289
|
+
|
2290
|
+
range = widget.editor.createRange();
|
2291
|
+
range.selectNodeContents( focusedEditable );
|
2292
|
+
// Exclude bogus if exists.
|
2293
|
+
if ( bogus )
|
2294
|
+
range.setEndAt( bogus, CKEDITOR.POSITION_BEFORE_START );
|
2295
|
+
|
2296
|
+
range.select();
|
2297
|
+
// Cancel event - block default.
|
2298
|
+
return false;
|
2299
|
+
}
|
2300
|
+
// DEL or BACKSPACE.
|
2301
|
+
else if ( keyCode == 8 || keyCode == 46 ) {
|
2302
|
+
var ranges = widget.editor.getSelection().getRanges();
|
2303
|
+
|
2304
|
+
range = ranges[ 0 ];
|
2305
|
+
|
2306
|
+
// Block del or backspace if at editable's boundary.
|
2307
|
+
return !( ranges.length == 1 && range.collapsed &&
|
2308
|
+
range.checkBoundaryOfElement( focusedEditable, CKEDITOR[ keyCode == 8 ? 'START' : 'END' ] ) );
|
2309
|
+
}
|
2310
|
+
}
|
2311
|
+
|
2312
|
+
function setFocusedEditable( widgetsRepo, widget, editableElement, offline ) {
|
2313
|
+
var editor = widgetsRepo.editor;
|
2314
|
+
|
2315
|
+
editor.fire( 'lockSnapshot' );
|
2316
|
+
|
2317
|
+
if ( editableElement ) {
|
2318
|
+
var editableName = editableElement.data( 'cke-widget-editable' ),
|
2319
|
+
editableInstance = widget.editables[ editableName ];
|
2320
|
+
|
2321
|
+
widgetsRepo.widgetHoldingFocusedEditable = widget;
|
2322
|
+
widget.focusedEditable = editableInstance;
|
2323
|
+
editableElement.addClass( 'cke_widget_editable_focused' );
|
2324
|
+
|
2325
|
+
if ( editableInstance.filter )
|
2326
|
+
editor.setActiveFilter( editableInstance.filter );
|
2327
|
+
editor.setActiveEnterMode( editableInstance.enterMode, editableInstance.shiftEnterMode );
|
2328
|
+
} else {
|
2329
|
+
if ( !offline )
|
2330
|
+
widget.focusedEditable.removeClass( 'cke_widget_editable_focused' );
|
2331
|
+
|
2332
|
+
widget.focusedEditable = null;
|
2333
|
+
widgetsRepo.widgetHoldingFocusedEditable = null;
|
2334
|
+
editor.setActiveFilter( null );
|
2335
|
+
editor.setActiveEnterMode( null, null );
|
2336
|
+
}
|
2337
|
+
|
2338
|
+
editor.fire( 'unlockSnapshot' );
|
2339
|
+
}
|
2340
|
+
|
2341
|
+
function setupContextMenu( editor ) {
|
2342
|
+
if ( !editor.contextMenu )
|
2343
|
+
return;
|
2344
|
+
|
2345
|
+
editor.contextMenu.addListener( function( element ) {
|
2346
|
+
var widget = editor.widgets.getByElement( element, true );
|
2347
|
+
|
2348
|
+
if ( widget )
|
2349
|
+
return widget.fire( 'contextMenu', {} );
|
2350
|
+
} );
|
2351
|
+
}
|
2352
|
+
|
2353
|
+
// And now we've got two problems - original problem and RegExp.
|
2354
|
+
// Some softeners:
|
2355
|
+
// * FF tends to copy all blocks up to the copybin container.
|
2356
|
+
// * IE tends to copy only the copybin, without its container.
|
2357
|
+
// * We use spans on IE and blockless editors, but divs in other cases.
|
2358
|
+
var pasteReplaceRegex = new RegExp(
|
2359
|
+
'^' +
|
2360
|
+
'(?:<(?:div|span)(?: data-cke-temp="1")?(?: id="cke_copybin")?(?: data-cke-temp="1")?>)?' +
|
2361
|
+
'(?:<(?:div|span)(?: style="[^"]+")?>)?' +
|
2362
|
+
'<span [^>]*data-cke-copybin-start="1"[^>]*>.?</span>([\\s\\S]+)<span [^>]*data-cke-copybin-end="1"[^>]*>.?</span>' +
|
2363
|
+
'(?:</(?:div|span)>)?' +
|
2364
|
+
'(?:</(?:div|span)>)?' +
|
2365
|
+
'$',
|
2366
|
+
// IE8 prefers uppercase when browsers stick to lowercase HTML (http://dev.ckeditor.com/ticket/13460).
|
2367
|
+
'i'
|
2368
|
+
);
|
2369
|
+
|
2370
|
+
function pasteReplaceFn( match, wrapperHtml ) {
|
2371
|
+
// Avoid polluting pasted data with any whitspaces,
|
2372
|
+
// what's going to break check whether only one widget was pasted.
|
2373
|
+
return CKEDITOR.tools.trim( wrapperHtml );
|
2374
|
+
}
|
2375
|
+
|
2376
|
+
function setupDragAndDrop( widgetsRepo ) {
|
2377
|
+
var editor = widgetsRepo.editor,
|
2378
|
+
lineutils = CKEDITOR.plugins.lineutils;
|
2379
|
+
|
2380
|
+
// These listeners handle inline and block widgets drag and drop.
|
2381
|
+
// The only thing we need to do to make block widgets custom drag and drop functionality
|
2382
|
+
// is to fire those events with the right properties (like the target which must be the drag handle).
|
2383
|
+
editor.on( 'dragstart', function( evt ) {
|
2384
|
+
var target = evt.data.target;
|
2385
|
+
|
2386
|
+
if ( Widget.isDomDragHandler( target ) ) {
|
2387
|
+
var widget = widgetsRepo.getByElement( target );
|
2388
|
+
|
2389
|
+
evt.data.dataTransfer.setData( 'cke/widget-id', widget.id );
|
2390
|
+
|
2391
|
+
// IE needs focus.
|
2392
|
+
editor.focus();
|
2393
|
+
|
2394
|
+
// and widget need to be focused on drag start (http://dev.ckeditor.com/ticket/12172#comment:10).
|
2395
|
+
widget.focus();
|
2396
|
+
}
|
2397
|
+
} );
|
2398
|
+
|
2399
|
+
editor.on( 'drop', function( evt ) {
|
2400
|
+
var dataTransfer = evt.data.dataTransfer,
|
2401
|
+
id = dataTransfer.getData( 'cke/widget-id' ),
|
2402
|
+
transferType = dataTransfer.getTransferType( editor ),
|
2403
|
+
dragRange = editor.createRange(),
|
2404
|
+
sourceWidget;
|
2405
|
+
|
2406
|
+
// Disable cross-editor drag & drop for widgets - http://dev.ckeditor.com/ticket/13599.
|
2407
|
+
if ( id !== '' && transferType === CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) {
|
2408
|
+
evt.cancel();
|
2409
|
+
return;
|
2410
|
+
}
|
2411
|
+
|
2412
|
+
if ( id === '' || transferType != CKEDITOR.DATA_TRANSFER_INTERNAL ) {
|
2413
|
+
return;
|
2414
|
+
}
|
2415
|
+
|
2416
|
+
sourceWidget = widgetsRepo.instances[ id ];
|
2417
|
+
if ( !sourceWidget ) {
|
2418
|
+
return;
|
2419
|
+
}
|
2420
|
+
|
2421
|
+
dragRange.setStartBefore( sourceWidget.wrapper );
|
2422
|
+
dragRange.setEndAfter( sourceWidget.wrapper );
|
2423
|
+
evt.data.dragRange = dragRange;
|
2424
|
+
|
2425
|
+
// [IE8-9] Reset state of the clipboard#fixSplitNodesAfterDrop fix because by setting evt.data.dragRange
|
2426
|
+
// (see above) after drop happened we do not need it. That fix is needed only if dragRange was created
|
2427
|
+
// before drop (before text node was split).
|
2428
|
+
delete CKEDITOR.plugins.clipboard.dragStartContainerChildCount;
|
2429
|
+
delete CKEDITOR.plugins.clipboard.dragEndContainerChildCount;
|
2430
|
+
|
2431
|
+
evt.data.dataTransfer.setData( 'text/html', editor.editable().getHtmlFromRange( dragRange ).getHtml() );
|
2432
|
+
editor.widgets.destroy( sourceWidget, true );
|
2433
|
+
} );
|
2434
|
+
|
2435
|
+
editor.on( 'contentDom', function() {
|
2436
|
+
var editable = editor.editable();
|
2437
|
+
|
2438
|
+
// Register Lineutils's utilities as properties of repo.
|
2439
|
+
CKEDITOR.tools.extend( widgetsRepo, {
|
2440
|
+
finder: new lineutils.finder( editor, {
|
2441
|
+
lookups: {
|
2442
|
+
// Element is block but not list item and not in nested editable.
|
2443
|
+
'default': function( el ) {
|
2444
|
+
if ( el.is( CKEDITOR.dtd.$listItem ) )
|
2445
|
+
return;
|
2446
|
+
|
2447
|
+
if ( !el.is( CKEDITOR.dtd.$block ) )
|
2448
|
+
return;
|
2449
|
+
|
2450
|
+
// Allow drop line inside, but never before or after nested editable (http://dev.ckeditor.com/ticket/12006).
|
2451
|
+
if ( Widget.isDomNestedEditable( el ) )
|
2452
|
+
return;
|
2453
|
+
|
2454
|
+
// Do not allow droping inside the widget being dragged (http://dev.ckeditor.com/ticket/13397).
|
2455
|
+
if ( widgetsRepo._.draggedWidget.wrapper.contains( el ) ) {
|
2456
|
+
return;
|
2457
|
+
}
|
2458
|
+
|
2459
|
+
// If element is nested editable, make sure widget can be dropped there (http://dev.ckeditor.com/ticket/12006).
|
2460
|
+
var nestedEditable = Widget.getNestedEditable( editable, el );
|
2461
|
+
if ( nestedEditable ) {
|
2462
|
+
var draggedWidget = widgetsRepo._.draggedWidget;
|
2463
|
+
|
2464
|
+
// Don't let the widget to be dropped into its own nested editable.
|
2465
|
+
if ( widgetsRepo.getByElement( nestedEditable ) == draggedWidget )
|
2466
|
+
return;
|
2467
|
+
|
2468
|
+
var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ],
|
2469
|
+
draggedRequiredContent = draggedWidget.requiredContent;
|
2470
|
+
|
2471
|
+
// There will be no relation if the filter of nested editable does not allow
|
2472
|
+
// requiredContent of dragged widget.
|
2473
|
+
if ( filter && draggedRequiredContent && !filter.check( draggedRequiredContent ) )
|
2474
|
+
return;
|
2475
|
+
}
|
2476
|
+
|
2477
|
+
return CKEDITOR.LINEUTILS_BEFORE | CKEDITOR.LINEUTILS_AFTER;
|
2478
|
+
}
|
2479
|
+
}
|
2480
|
+
} ),
|
2481
|
+
locator: new lineutils.locator( editor ),
|
2482
|
+
liner: new lineutils.liner( editor, {
|
2483
|
+
lineStyle: {
|
2484
|
+
cursor: 'move !important',
|
2485
|
+
'border-top-color': '#666'
|
2486
|
+
},
|
2487
|
+
tipLeftStyle: {
|
2488
|
+
'border-left-color': '#666'
|
2489
|
+
},
|
2490
|
+
tipRightStyle: {
|
2491
|
+
'border-right-color': '#666'
|
2492
|
+
}
|
2493
|
+
} )
|
2494
|
+
}, true );
|
2495
|
+
} );
|
2496
|
+
}
|
2497
|
+
|
2498
|
+
// Setup mouse observer which will trigger:
|
2499
|
+
// * widget focus on widget click,
|
2500
|
+
// * widget#doubleclick forwarded from editor#doubleclick.
|
2501
|
+
function setupMouseObserver( widgetsRepo ) {
|
2502
|
+
var editor = widgetsRepo.editor;
|
2503
|
+
|
2504
|
+
editor.on( 'contentDom', function() {
|
2505
|
+
var editable = editor.editable(),
|
2506
|
+
evtRoot = editable.isInline() ? editable : editor.document,
|
2507
|
+
widget,
|
2508
|
+
mouseDownOnDragHandler;
|
2509
|
+
|
2510
|
+
editable.attachListener( evtRoot, 'mousedown', function( evt ) {
|
2511
|
+
var target = evt.data.getTarget();
|
2512
|
+
|
2513
|
+
// Clicking scrollbar in Chrome will invoke event with target object of document type (#663).
|
2514
|
+
// In IE8 the target object will be empty (http://dev.ckeditor.com/ticket/10887).
|
2515
|
+
// We need to check if target is a proper element.
|
2516
|
+
widget = ( target instanceof CKEDITOR.dom.element ) ? widgetsRepo.getByElement( target ) : null;
|
2517
|
+
|
2518
|
+
mouseDownOnDragHandler = 0; // Reset.
|
2519
|
+
|
2520
|
+
// Widget was clicked, but not editable nested in it.
|
2521
|
+
if ( widget ) {
|
2522
|
+
// Ignore mousedown on drag and drop handler if the widget is inline.
|
2523
|
+
// Block widgets are handled by Lineutils.
|
2524
|
+
if ( widget.inline && target.type == CKEDITOR.NODE_ELEMENT && target.hasAttribute( 'data-cke-widget-drag-handler' ) ) {
|
2525
|
+
mouseDownOnDragHandler = 1;
|
2526
|
+
|
2527
|
+
// When drag handler is pressed we have to clear current selection if it wasn't already on this widget.
|
2528
|
+
// Otherwise, the selection may be in a fillingChar, which prevents dragging a widget. (http://dev.ckeditor.com/ticket/13284, see comment 8 and 9.)
|
2529
|
+
if ( widgetsRepo.focused != widget )
|
2530
|
+
editor.getSelection().removeAllRanges();
|
2531
|
+
|
2532
|
+
return;
|
2533
|
+
}
|
2534
|
+
|
2535
|
+
if ( !Widget.getNestedEditable( widget.wrapper, target ) ) {
|
2536
|
+
evt.data.preventDefault();
|
2537
|
+
if ( !CKEDITOR.env.ie )
|
2538
|
+
widget.focus();
|
2539
|
+
} else {
|
2540
|
+
// Reset widget so mouseup listener is not confused.
|
2541
|
+
widget = null;
|
2542
|
+
}
|
2543
|
+
}
|
2544
|
+
} );
|
2545
|
+
|
2546
|
+
// Focus widget on mouseup if mousedown was fired on drag handler.
|
2547
|
+
// Note: mouseup won't be fired at all if widget was dragged and dropped, so
|
2548
|
+
// this code will be executed only when drag handler was clicked.
|
2549
|
+
editable.attachListener( evtRoot, 'mouseup', function() {
|
2550
|
+
// Check if widget is not destroyed (if widget is destroyed the wrapper will be null).
|
2551
|
+
if ( mouseDownOnDragHandler && widget && widget.wrapper ) {
|
2552
|
+
mouseDownOnDragHandler = 0;
|
2553
|
+
widget.focus();
|
2554
|
+
}
|
2555
|
+
} );
|
2556
|
+
|
2557
|
+
// On IE it is not enough to block mousedown. If widget wrapper (element with
|
2558
|
+
// contenteditable=false attribute) is clicked directly (it is a target),
|
2559
|
+
// then after mouseup/click IE will select that element.
|
2560
|
+
// It is not possible to prevent that default action,
|
2561
|
+
// so we force fake selection after everything happened.
|
2562
|
+
if ( CKEDITOR.env.ie ) {
|
2563
|
+
editable.attachListener( evtRoot, 'mouseup', function() {
|
2564
|
+
setTimeout( function() {
|
2565
|
+
// Check if widget is not destroyed (if widget is destroyed the wrapper will be null) and
|
2566
|
+
// in editable contains widget (it could be dragged and removed).
|
2567
|
+
if ( widget && widget.wrapper && editable.contains( widget.wrapper ) ) {
|
2568
|
+
widget.focus();
|
2569
|
+
widget = null;
|
2570
|
+
}
|
2571
|
+
} );
|
2572
|
+
} );
|
2573
|
+
}
|
2574
|
+
} );
|
2575
|
+
|
2576
|
+
editor.on( 'doubleclick', function( evt ) {
|
2577
|
+
var widget = widgetsRepo.getByElement( evt.data.element );
|
2578
|
+
|
2579
|
+
// Not in widget or in nested editable.
|
2580
|
+
if ( !widget || Widget.getNestedEditable( widget.wrapper, evt.data.element ) )
|
2581
|
+
return;
|
2582
|
+
|
2583
|
+
return widget.fire( 'doubleclick', { element: evt.data.element } );
|
2584
|
+
}, null, null, 1 );
|
2585
|
+
}
|
2586
|
+
|
2587
|
+
// Setup editor#key observer which will forward it
|
2588
|
+
// to focused widget.
|
2589
|
+
function setupKeyboardObserver( widgetsRepo ) {
|
2590
|
+
var editor = widgetsRepo.editor;
|
2591
|
+
|
2592
|
+
editor.on( 'key', function( evt ) {
|
2593
|
+
var focused = widgetsRepo.focused,
|
2594
|
+
widgetHoldingFocusedEditable = widgetsRepo.widgetHoldingFocusedEditable,
|
2595
|
+
ret;
|
2596
|
+
|
2597
|
+
if ( focused )
|
2598
|
+
ret = focused.fire( 'key', { keyCode: evt.data.keyCode } );
|
2599
|
+
else if ( widgetHoldingFocusedEditable )
|
2600
|
+
ret = onEditableKey( widgetHoldingFocusedEditable, evt.data.keyCode );
|
2601
|
+
|
2602
|
+
return ret;
|
2603
|
+
}, null, null, 1 );
|
2604
|
+
}
|
2605
|
+
|
2606
|
+
// Setup copybin on native copy and cut events in order to handle copy and cut commands
|
2607
|
+
// if user accepted security alert on IEs.
|
2608
|
+
// Note: when copying or cutting using keystroke, copySingleWidget will be first executed
|
2609
|
+
// by the keydown listener. Conflict between two calls will be resolved by copy_bin existence check.
|
2610
|
+
function setupNativeCutAndCopy( widgetsRepo ) {
|
2611
|
+
var editor = widgetsRepo.editor;
|
2612
|
+
|
2613
|
+
editor.on( 'contentDom', function() {
|
2614
|
+
var editable = editor.editable();
|
2615
|
+
|
2616
|
+
editable.attachListener( editable, 'copy', eventListener );
|
2617
|
+
editable.attachListener( editable, 'cut', eventListener );
|
2618
|
+
} );
|
2619
|
+
|
2620
|
+
function eventListener( evt ) {
|
2621
|
+
if ( widgetsRepo.focused )
|
2622
|
+
copySingleWidget( widgetsRepo.focused, evt.name == 'cut' );
|
2623
|
+
}
|
2624
|
+
}
|
2625
|
+
|
2626
|
+
// Setup selection observer which will trigger:
|
2627
|
+
// * widget select & focus on selection change,
|
2628
|
+
// * nested editable focus (related properites and classes) on selection change,
|
2629
|
+
// * deselecting and blurring all widgets on data,
|
2630
|
+
// * blurring widget on editor blur.
|
2631
|
+
function setupSelectionObserver( widgetsRepo ) {
|
2632
|
+
var editor = widgetsRepo.editor;
|
2633
|
+
|
2634
|
+
editor.on( 'selectionCheck', function() {
|
2635
|
+
widgetsRepo.fire( 'checkSelection' );
|
2636
|
+
} );
|
2637
|
+
|
2638
|
+
widgetsRepo.on( 'checkSelection', widgetsRepo.checkSelection, widgetsRepo );
|
2639
|
+
|
2640
|
+
editor.on( 'selectionChange', function( evt ) {
|
2641
|
+
var nestedEditable = Widget.getNestedEditable( editor.editable(), evt.data.selection.getStartElement() ),
|
2642
|
+
newWidget = nestedEditable && widgetsRepo.getByElement( nestedEditable ),
|
2643
|
+
oldWidget = widgetsRepo.widgetHoldingFocusedEditable;
|
2644
|
+
|
2645
|
+
if ( oldWidget ) {
|
2646
|
+
if ( oldWidget !== newWidget || !oldWidget.focusedEditable.equals( nestedEditable ) ) {
|
2647
|
+
setFocusedEditable( widgetsRepo, oldWidget, null );
|
2648
|
+
|
2649
|
+
if ( newWidget && nestedEditable )
|
2650
|
+
setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
|
2651
|
+
}
|
2652
|
+
}
|
2653
|
+
// It may happen that there's no widget even if editable was found -
|
2654
|
+
// e.g. if selection was automatically set in editable although widget wasn't initialized yet.
|
2655
|
+
else if ( newWidget && nestedEditable ) {
|
2656
|
+
setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
|
2657
|
+
}
|
2658
|
+
} );
|
2659
|
+
|
2660
|
+
// Invalidate old widgets early - immediately on dataReady.
|
2661
|
+
editor.on( 'dataReady', function() {
|
2662
|
+
// Deselect and blur all widgets.
|
2663
|
+
stateUpdater( widgetsRepo ).commit();
|
2664
|
+
} );
|
2665
|
+
|
2666
|
+
editor.on( 'blur', function() {
|
2667
|
+
var widget;
|
2668
|
+
|
2669
|
+
if ( ( widget = widgetsRepo.focused ) )
|
2670
|
+
blurWidget( widgetsRepo, widget );
|
2671
|
+
|
2672
|
+
if ( ( widget = widgetsRepo.widgetHoldingFocusedEditable ) )
|
2673
|
+
setFocusedEditable( widgetsRepo, widget, null );
|
2674
|
+
} );
|
2675
|
+
}
|
2676
|
+
|
2677
|
+
// Set up actions like:
|
2678
|
+
// * processing in toHtml/toDataFormat,
|
2679
|
+
// * pasting handling,
|
2680
|
+
// * insertion handling,
|
2681
|
+
// * editable reload handling (setData, mode switch, undo/redo),
|
2682
|
+
// * DOM invalidation handling,
|
2683
|
+
// * widgets checks.
|
2684
|
+
function setupWidgetsLifecycle( widgetsRepo ) {
|
2685
|
+
setupWidgetsLifecycleStart( widgetsRepo );
|
2686
|
+
setupWidgetsLifecycleEnd( widgetsRepo );
|
2687
|
+
|
2688
|
+
widgetsRepo.on( 'checkWidgets', checkWidgets );
|
2689
|
+
widgetsRepo.editor.on( 'contentDomInvalidated', widgetsRepo.checkWidgets, widgetsRepo );
|
2690
|
+
}
|
2691
|
+
|
2692
|
+
function setupWidgetsLifecycleEnd( widgetsRepo ) {
|
2693
|
+
var editor = widgetsRepo.editor,
|
2694
|
+
downcastingSessions = {};
|
2695
|
+
|
2696
|
+
// Listen before htmlDP#htmlFilter is applied to cache all widgets, because we'll
|
2697
|
+
// loose data-cke-* attributes.
|
2698
|
+
editor.on( 'toDataFormat', function( evt ) {
|
2699
|
+
// To avoid conflicts between htmlDP#toDF calls done at the same time
|
2700
|
+
// (e.g. nestedEditable#getData called during downcasting some widget)
|
2701
|
+
// mark every toDataFormat event chain with the downcasting session id.
|
2702
|
+
var id = CKEDITOR.tools.getNextNumber(),
|
2703
|
+
toBeDowncasted = [];
|
2704
|
+
evt.data.downcastingSessionId = id;
|
2705
|
+
downcastingSessions[ id ] = toBeDowncasted;
|
2706
|
+
|
2707
|
+
evt.data.dataValue.forEach( function( element ) {
|
2708
|
+
var attrs = element.attributes,
|
2709
|
+
widget, widgetElement;
|
2710
|
+
|
2711
|
+
// Wrapper.
|
2712
|
+
// Perform first part of downcasting (cleanup) and cache widgets,
|
2713
|
+
// because after applying DP's filter all data-cke-* attributes will be gone.
|
2714
|
+
if ( 'data-cke-widget-id' in attrs ) {
|
2715
|
+
widget = widgetsRepo.instances[ attrs[ 'data-cke-widget-id' ] ];
|
2716
|
+
if ( widget ) {
|
2717
|
+
widgetElement = element.getFirst( Widget.isParserWidgetElement );
|
2718
|
+
toBeDowncasted.push( {
|
2719
|
+
wrapper: element,
|
2720
|
+
element: widgetElement,
|
2721
|
+
widget: widget,
|
2722
|
+
editables: {}
|
2723
|
+
} );
|
2724
|
+
|
2725
|
+
// If widget did not have data-cke-widget attribute before upcasting remove it.
|
2726
|
+
if ( widgetElement.attributes[ 'data-cke-widget-keep-attr' ] != '1' )
|
2727
|
+
delete widgetElement.attributes[ 'data-widget' ];
|
2728
|
+
}
|
2729
|
+
}
|
2730
|
+
// Nested editable.
|
2731
|
+
else if ( 'data-cke-widget-editable' in attrs ) {
|
2732
|
+
// Save the reference to this nested editable in the closest widget to be downcasted.
|
2733
|
+
// Nested editables are downcasted in the successive toDataFormat to create an opportunity
|
2734
|
+
// for dataFilter's "excludeNestedEditable" option to do its job (that option relies on
|
2735
|
+
// contenteditable="true" attribute) (http://dev.ckeditor.com/ticket/11372).
|
2736
|
+
toBeDowncasted[ toBeDowncasted.length - 1 ].editables[ attrs[ 'data-cke-widget-editable' ] ] = element;
|
2737
|
+
|
2738
|
+
// Don't check children - there won't be next wrapper or nested editable which we
|
2739
|
+
// should process in this session.
|
2740
|
+
return false;
|
2741
|
+
}
|
2742
|
+
}, CKEDITOR.NODE_ELEMENT, true );
|
2743
|
+
}, null, null, 8 );
|
2744
|
+
|
2745
|
+
// Listen after dataProcessor.htmlFilter and ACF were applied
|
2746
|
+
// so wrappers securing widgets' contents are removed after all filtering was done.
|
2747
|
+
editor.on( 'toDataFormat', function( evt ) {
|
2748
|
+
// Ignore some unmarked sessions.
|
2749
|
+
if ( !evt.data.downcastingSessionId )
|
2750
|
+
return;
|
2751
|
+
|
2752
|
+
var toBeDowncasted = downcastingSessions[ evt.data.downcastingSessionId ],
|
2753
|
+
toBe, widget, widgetElement, retElement, editableElement, e;
|
2754
|
+
|
2755
|
+
while ( ( toBe = toBeDowncasted.shift() ) ) {
|
2756
|
+
widget = toBe.widget;
|
2757
|
+
widgetElement = toBe.element;
|
2758
|
+
retElement = widget._.downcastFn && widget._.downcastFn.call( widget, widgetElement );
|
2759
|
+
|
2760
|
+
// Replace nested editables' content with their output data.
|
2761
|
+
for ( e in toBe.editables ) {
|
2762
|
+
editableElement = toBe.editables[ e ];
|
2763
|
+
|
2764
|
+
delete editableElement.attributes.contenteditable;
|
2765
|
+
editableElement.setHtml( widget.editables[ e ].getData() );
|
2766
|
+
}
|
2767
|
+
|
2768
|
+
// Returned element always defaults to widgetElement.
|
2769
|
+
if ( !retElement )
|
2770
|
+
retElement = widgetElement;
|
2771
|
+
|
2772
|
+
toBe.wrapper.replaceWith( retElement );
|
2773
|
+
}
|
2774
|
+
}, null, null, 13 );
|
2775
|
+
|
2776
|
+
|
2777
|
+
editor.on( 'contentDomUnload', function() {
|
2778
|
+
widgetsRepo.destroyAll( true );
|
2779
|
+
} );
|
2780
|
+
}
|
2781
|
+
|
2782
|
+
function setupWidgetsLifecycleStart( widgetsRepo ) {
|
2783
|
+
var editor = widgetsRepo.editor,
|
2784
|
+
processedWidgetOnly,
|
2785
|
+
snapshotLoaded;
|
2786
|
+
|
2787
|
+
// Listen after ACF (so data are filtered),
|
2788
|
+
// but before dataProcessor.dataFilter was applied (so we can secure widgets' internals).
|
2789
|
+
editor.on( 'toHtml', function( evt ) {
|
2790
|
+
var upcastIterator = createUpcastIterator( widgetsRepo ),
|
2791
|
+
toBeWrapped;
|
2792
|
+
|
2793
|
+
evt.data.dataValue.forEach( upcastIterator.iterator, CKEDITOR.NODE_ELEMENT, true );
|
2794
|
+
|
2795
|
+
// Clean up and wrap all queued elements.
|
2796
|
+
while ( ( toBeWrapped = upcastIterator.toBeWrapped.pop() ) ) {
|
2797
|
+
cleanUpWidgetElement( toBeWrapped[ 0 ] );
|
2798
|
+
widgetsRepo.wrapElement( toBeWrapped[ 0 ], toBeWrapped[ 1 ] );
|
2799
|
+
}
|
2800
|
+
|
2801
|
+
// Used to determine whether only widget was pasted.
|
2802
|
+
if ( evt.data.protectedWhitespaces ) {
|
2803
|
+
// Whitespaces are protected by wrapping content with spans. Take the middle node only.
|
2804
|
+
processedWidgetOnly = evt.data.dataValue.children.length == 3 &&
|
2805
|
+
Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 1 ] );
|
2806
|
+
} else {
|
2807
|
+
processedWidgetOnly = evt.data.dataValue.children.length == 1 &&
|
2808
|
+
Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 0 ] );
|
2809
|
+
}
|
2810
|
+
}, null, null, 8 );
|
2811
|
+
|
2812
|
+
editor.on( 'dataReady', function() {
|
2813
|
+
// Clean up all widgets loaded from snapshot.
|
2814
|
+
if ( snapshotLoaded )
|
2815
|
+
cleanUpAllWidgetElements( widgetsRepo, editor.editable() );
|
2816
|
+
snapshotLoaded = 0;
|
2817
|
+
|
2818
|
+
// Some widgets were destroyed on contentDomUnload,
|
2819
|
+
// some on loadSnapshot, but that does not include
|
2820
|
+
// e.g. setHtml on inline editor or widgets removed just
|
2821
|
+
// before setting data.
|
2822
|
+
widgetsRepo.destroyAll( true );
|
2823
|
+
widgetsRepo.initOnAll();
|
2824
|
+
} );
|
2825
|
+
|
2826
|
+
// Set flag so dataReady will know that additional
|
2827
|
+
// cleanup is needed, because snapshot containing widgets was loaded.
|
2828
|
+
editor.on( 'loadSnapshot', function( evt ) {
|
2829
|
+
// Primitive but sufficient check which will prevent from executing
|
2830
|
+
// heavier cleanUpAllWidgetElements if not needed.
|
2831
|
+
if ( ( /data-cke-widget/ ).test( evt.data ) )
|
2832
|
+
snapshotLoaded = 1;
|
2833
|
+
|
2834
|
+
widgetsRepo.destroyAll( true );
|
2835
|
+
}, null, null, 9 );
|
2836
|
+
|
2837
|
+
// Handle pasted single widget.
|
2838
|
+
editor.on( 'paste', function( evt ) {
|
2839
|
+
var data = evt.data;
|
2840
|
+
|
2841
|
+
data.dataValue = data.dataValue.replace( pasteReplaceRegex, pasteReplaceFn );
|
2842
|
+
|
2843
|
+
// If drag'n'drop kind of paste into nested editable (data.range), selection is set AFTER
|
2844
|
+
// data is pasted, which means editor has no chance to change activeFilter's context.
|
2845
|
+
// As a result, pasted data is filtered with default editor's filter instead of NE's and
|
2846
|
+
// funny things get inserted. Changing the filter by analysis of the paste range below (http://dev.ckeditor.com/ticket/13186).
|
2847
|
+
if ( data.range ) {
|
2848
|
+
// Check if pasting into nested editable.
|
2849
|
+
var nestedEditable = Widget.getNestedEditable( editor.editable(), data.range.startContainer );
|
2850
|
+
|
2851
|
+
if ( nestedEditable ) {
|
2852
|
+
// Retrieve the filter from NE's data and set it active before editor.insertHtml is done
|
2853
|
+
// in clipboard plugin.
|
2854
|
+
var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ];
|
2855
|
+
|
2856
|
+
if ( filter ) {
|
2857
|
+
editor.setActiveFilter( filter );
|
2858
|
+
}
|
2859
|
+
}
|
2860
|
+
}
|
2861
|
+
} );
|
2862
|
+
|
2863
|
+
// Listen with high priority to check widgets after data was inserted.
|
2864
|
+
editor.on( 'afterInsertHtml', function( evt ) {
|
2865
|
+
if ( evt.data.intoRange ) {
|
2866
|
+
widgetsRepo.checkWidgets( { initOnlyNew: true } );
|
2867
|
+
} else {
|
2868
|
+
editor.fire( 'lockSnapshot' );
|
2869
|
+
// Init only new for performance reason.
|
2870
|
+
// Focus inited if only widget was processed.
|
2871
|
+
widgetsRepo.checkWidgets( { initOnlyNew: true, focusInited: processedWidgetOnly } );
|
2872
|
+
|
2873
|
+
editor.fire( 'unlockSnapshot' );
|
2874
|
+
}
|
2875
|
+
} );
|
2876
|
+
}
|
2877
|
+
|
2878
|
+
// Helper for coordinating which widgets should be
|
2879
|
+
// selected/deselected and which one should be focused/blurred.
|
2880
|
+
function stateUpdater( widgetsRepo ) {
|
2881
|
+
var currentlySelected = widgetsRepo.selected,
|
2882
|
+
toBeSelected = [],
|
2883
|
+
toBeDeselected = currentlySelected.slice( 0 ),
|
2884
|
+
focused = null;
|
2885
|
+
|
2886
|
+
return {
|
2887
|
+
select: function( widget ) {
|
2888
|
+
if ( CKEDITOR.tools.indexOf( currentlySelected, widget ) < 0 )
|
2889
|
+
toBeSelected.push( widget );
|
2890
|
+
|
2891
|
+
var index = CKEDITOR.tools.indexOf( toBeDeselected, widget );
|
2892
|
+
if ( index >= 0 )
|
2893
|
+
toBeDeselected.splice( index, 1 );
|
2894
|
+
|
2895
|
+
return this;
|
2896
|
+
},
|
2897
|
+
|
2898
|
+
focus: function( widget ) {
|
2899
|
+
focused = widget;
|
2900
|
+
return this;
|
2901
|
+
},
|
2902
|
+
|
2903
|
+
commit: function() {
|
2904
|
+
var focusedChanged = widgetsRepo.focused !== focused,
|
2905
|
+
widget, isDirty;
|
2906
|
+
|
2907
|
+
widgetsRepo.editor.fire( 'lockSnapshot' );
|
2908
|
+
|
2909
|
+
if ( focusedChanged && ( widget = widgetsRepo.focused ) )
|
2910
|
+
blurWidget( widgetsRepo, widget );
|
2911
|
+
|
2912
|
+
while ( ( widget = toBeDeselected.pop() ) ) {
|
2913
|
+
currentlySelected.splice( CKEDITOR.tools.indexOf( currentlySelected, widget ), 1 );
|
2914
|
+
// Widget could be destroyed in the meantime - e.g. data could be set.
|
2915
|
+
if ( widget.isInited() ) {
|
2916
|
+
isDirty = widget.editor.checkDirty();
|
2917
|
+
|
2918
|
+
widget.setSelected( false );
|
2919
|
+
|
2920
|
+
!isDirty && widget.editor.resetDirty();
|
2921
|
+
}
|
2922
|
+
}
|
2923
|
+
|
2924
|
+
if ( focusedChanged && focused ) {
|
2925
|
+
isDirty = widgetsRepo.editor.checkDirty();
|
2926
|
+
|
2927
|
+
widgetsRepo.focused = focused;
|
2928
|
+
widgetsRepo.fire( 'widgetFocused', { widget: focused } );
|
2929
|
+
focused.setFocused( true );
|
2930
|
+
|
2931
|
+
!isDirty && widgetsRepo.editor.resetDirty();
|
2932
|
+
}
|
2933
|
+
|
2934
|
+
while ( ( widget = toBeSelected.pop() ) ) {
|
2935
|
+
currentlySelected.push( widget );
|
2936
|
+
widget.setSelected( true );
|
2937
|
+
}
|
2938
|
+
|
2939
|
+
widgetsRepo.editor.fire( 'unlockSnapshot' );
|
2940
|
+
}
|
2941
|
+
};
|
2942
|
+
}
|
2943
|
+
|
2944
|
+
|
2945
|
+
//
|
2946
|
+
// WIDGET helpers ---------------------------------------------------------
|
2947
|
+
//
|
2948
|
+
|
2949
|
+
// LEFT, RIGHT, UP, DOWN, DEL, BACKSPACE - unblock default fake sel handlers.
|
2950
|
+
var keystrokesNotBlockedByWidget = { 37: 1, 38: 1, 39: 1, 40: 1, 8: 1, 46: 1 };
|
2951
|
+
|
2952
|
+
// Applies or removes style's classes from widget.
|
2953
|
+
// @param {CKEDITOR.style} style Custom widget style.
|
2954
|
+
// @param {Boolean} apply Whether to apply or remove style.
|
2955
|
+
function applyRemoveStyle( widget, style, apply ) {
|
2956
|
+
var changed = 0,
|
2957
|
+
classes = getStyleClasses( style ),
|
2958
|
+
updatedClasses = widget.data.classes || {},
|
2959
|
+
cl;
|
2960
|
+
|
2961
|
+
// Ee... Something is wrong with this style.
|
2962
|
+
if ( !classes )
|
2963
|
+
return;
|
2964
|
+
|
2965
|
+
// Clone, because we need to break reference.
|
2966
|
+
updatedClasses = CKEDITOR.tools.clone( updatedClasses );
|
2967
|
+
|
2968
|
+
while ( ( cl = classes.pop() ) ) {
|
2969
|
+
if ( apply ) {
|
2970
|
+
if ( !updatedClasses[ cl ] )
|
2971
|
+
changed = updatedClasses[ cl ] = 1;
|
2972
|
+
} else {
|
2973
|
+
if ( updatedClasses[ cl ] ) {
|
2974
|
+
delete updatedClasses[ cl ];
|
2975
|
+
changed = 1;
|
2976
|
+
}
|
2977
|
+
}
|
2978
|
+
}
|
2979
|
+
if ( changed )
|
2980
|
+
widget.setData( 'classes', updatedClasses );
|
2981
|
+
}
|
2982
|
+
|
2983
|
+
function cancel( evt ) {
|
2984
|
+
evt.cancel();
|
2985
|
+
}
|
2986
|
+
|
2987
|
+
function copySingleWidget( widget, isCut ) {
|
2988
|
+
var editor = widget.editor,
|
2989
|
+
doc = editor.document;
|
2990
|
+
|
2991
|
+
// We're still handling previous copy/cut.
|
2992
|
+
// When keystroke is used to copy/cut this will also prevent
|
2993
|
+
// conflict with copySingleWidget called again for native copy/cut event.
|
2994
|
+
if ( doc.getById( 'cke_copybin' ) )
|
2995
|
+
return;
|
2996
|
+
|
2997
|
+
// [IE] Use span for copybin and its container to avoid bug with expanding editable height by
|
2998
|
+
// absolutely positioned element.
|
2999
|
+
var copybinName = ( editor.blockless || CKEDITOR.env.ie ) ? 'span' : 'div',
|
3000
|
+
copybin = doc.createElement( copybinName ),
|
3001
|
+
copybinContainer = doc.createElement( copybinName ),
|
3002
|
+
// IE8 always jumps to the end of document.
|
3003
|
+
needsScrollHack = CKEDITOR.env.ie && CKEDITOR.env.version < 9;
|
3004
|
+
|
3005
|
+
copybinContainer.setAttributes( {
|
3006
|
+
id: 'cke_copybin',
|
3007
|
+
'data-cke-temp': '1'
|
3008
|
+
} );
|
3009
|
+
|
3010
|
+
// Position copybin element outside current viewport.
|
3011
|
+
copybin.setStyles( {
|
3012
|
+
position: 'absolute',
|
3013
|
+
width: '1px',
|
3014
|
+
height: '1px',
|
3015
|
+
overflow: 'hidden'
|
3016
|
+
} );
|
3017
|
+
|
3018
|
+
copybin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-5000px' );
|
3019
|
+
|
3020
|
+
var range = editor.createRange();
|
3021
|
+
range.setStartBefore( widget.wrapper );
|
3022
|
+
range.setEndAfter( widget.wrapper );
|
3023
|
+
|
3024
|
+
copybin.setHtml(
|
3025
|
+
'<span data-cke-copybin-start="1">\u200b</span>' +
|
3026
|
+
editor.editable().getHtmlFromRange( range ).getHtml() +
|
3027
|
+
'<span data-cke-copybin-end="1">\u200b</span>' );
|
3028
|
+
|
3029
|
+
// Save snapshot with the current state.
|
3030
|
+
editor.fire( 'saveSnapshot' );
|
3031
|
+
|
3032
|
+
// Ignore copybin.
|
3033
|
+
editor.fire( 'lockSnapshot' );
|
3034
|
+
|
3035
|
+
copybinContainer.append( copybin );
|
3036
|
+
editor.editable().append( copybinContainer );
|
3037
|
+
|
3038
|
+
var listener1 = editor.on( 'selectionChange', cancel, null, null, 0 ),
|
3039
|
+
listener2 = widget.repository.on( 'checkSelection', cancel, null, null, 0 );
|
3040
|
+
|
3041
|
+
if ( needsScrollHack ) {
|
3042
|
+
var docElement = doc.getDocumentElement().$,
|
3043
|
+
scrollTop = docElement.scrollTop;
|
3044
|
+
}
|
3045
|
+
|
3046
|
+
// Once the clone of the widget is inside of copybin, select
|
3047
|
+
// the entire contents. This selection will be copied by the
|
3048
|
+
// native browser's clipboard system.
|
3049
|
+
range = editor.createRange();
|
3050
|
+
range.selectNodeContents( copybin );
|
3051
|
+
range.select();
|
3052
|
+
|
3053
|
+
if ( needsScrollHack )
|
3054
|
+
docElement.scrollTop = scrollTop;
|
3055
|
+
|
3056
|
+
setTimeout( function() {
|
3057
|
+
// [IE] Focus widget before removing copybin to avoid scroll jump.
|
3058
|
+
if ( !isCut )
|
3059
|
+
widget.focus();
|
3060
|
+
|
3061
|
+
copybinContainer.remove();
|
3062
|
+
|
3063
|
+
listener1.removeListener();
|
3064
|
+
listener2.removeListener();
|
3065
|
+
|
3066
|
+
editor.fire( 'unlockSnapshot' );
|
3067
|
+
|
3068
|
+
if ( isCut ) {
|
3069
|
+
widget.repository.del( widget );
|
3070
|
+
editor.fire( 'saveSnapshot' );
|
3071
|
+
}
|
3072
|
+
}, 100 ); // Use 100ms, so Chrome (@Mac) will be able to grab the content.
|
3073
|
+
}
|
3074
|
+
|
3075
|
+
// Extracts classes array from style instance.
|
3076
|
+
function getStyleClasses( style ) {
|
3077
|
+
var attrs = style.getDefinition().attributes,
|
3078
|
+
classes = attrs && attrs[ 'class' ];
|
3079
|
+
|
3080
|
+
return classes ? classes.split( /\s+/ ) : null;
|
3081
|
+
}
|
3082
|
+
|
3083
|
+
// [IE] Force keeping focus because IE sometimes forgets to fire focus on main editable
|
3084
|
+
// when blurring nested editable.
|
3085
|
+
// @context widget
|
3086
|
+
function onEditableBlur() {
|
3087
|
+
var active = CKEDITOR.document.getActive(),
|
3088
|
+
editor = this.editor,
|
3089
|
+
editable = editor.editable();
|
3090
|
+
|
3091
|
+
// If focus stays within editor override blur and set currentActive because it should be
|
3092
|
+
// automatically changed to editable on editable#focus but it is not fired.
|
3093
|
+
if ( ( editable.isInline() ? editable : editor.document.getWindow().getFrame() ).equals( active ) )
|
3094
|
+
editor.focusManager.focus( editable );
|
3095
|
+
}
|
3096
|
+
|
3097
|
+
// Force selectionChange when editable was focused.
|
3098
|
+
// Similar to hack in selection.js#~620.
|
3099
|
+
// @context widget
|
3100
|
+
function onEditableFocus() {
|
3101
|
+
// Gecko does not support 'DOMFocusIn' event on which we unlock selection
|
3102
|
+
// in selection.js to prevent selection locking when entering nested editables.
|
3103
|
+
if ( CKEDITOR.env.gecko )
|
3104
|
+
this.editor.unlockSelection();
|
3105
|
+
|
3106
|
+
// We don't need to force selectionCheck on Webkit, because on Webkit
|
3107
|
+
// we do that on DOMFocusIn in selection.js.
|
3108
|
+
if ( !CKEDITOR.env.webkit ) {
|
3109
|
+
this.editor.forceNextSelectionCheck();
|
3110
|
+
this.editor.selectionChange( 1 );
|
3111
|
+
}
|
3112
|
+
}
|
3113
|
+
|
3114
|
+
// Setup listener on widget#data which will update (remove/add) classes
|
3115
|
+
// by comparing newly set classes with the old ones.
|
3116
|
+
function setupDataClassesListener( widget ) {
|
3117
|
+
// Note: previousClasses and newClasses may be null!
|
3118
|
+
// Tip: for ( cl in null ) is correct.
|
3119
|
+
var previousClasses = null;
|
3120
|
+
|
3121
|
+
widget.on( 'data', function() {
|
3122
|
+
var newClasses = this.data.classes,
|
3123
|
+
cl;
|
3124
|
+
|
3125
|
+
// When setting new classes one need to remember
|
3126
|
+
// that he must break reference.
|
3127
|
+
if ( previousClasses == newClasses )
|
3128
|
+
return;
|
3129
|
+
|
3130
|
+
for ( cl in previousClasses ) {
|
3131
|
+
// Avoid removing and adding classes again.
|
3132
|
+
if ( !( newClasses && newClasses[ cl ] ) )
|
3133
|
+
this.removeClass( cl );
|
3134
|
+
}
|
3135
|
+
for ( cl in newClasses )
|
3136
|
+
this.addClass( cl );
|
3137
|
+
|
3138
|
+
previousClasses = newClasses;
|
3139
|
+
} );
|
3140
|
+
}
|
3141
|
+
|
3142
|
+
// Add a listener to data event that will set/change widget's label (http://dev.ckeditor.com/ticket/14539).
|
3143
|
+
function setupA11yListener( widget ) {
|
3144
|
+
// Note, the function gets executed in a context of widget instance.
|
3145
|
+
function getLabelDefault() {
|
3146
|
+
return this.editor.lang.widget.label.replace( /%1/, this.pathName || this.element.getName() );
|
3147
|
+
}
|
3148
|
+
|
3149
|
+
// Setting a listener on data is enough, there's no need to perform it on widget initialization, as
|
3150
|
+
// setupWidgetData fires this event anyway.
|
3151
|
+
widget.on( 'data', function() {
|
3152
|
+
// In some cases widget might get destroyed in an earlier data listener. For instance, image2 plugin, does
|
3153
|
+
// so when changing its internal state.
|
3154
|
+
if ( !widget.wrapper ) {
|
3155
|
+
return;
|
3156
|
+
}
|
3157
|
+
|
3158
|
+
var label = this.getLabel ? this.getLabel() : getLabelDefault.call( this );
|
3159
|
+
|
3160
|
+
widget.wrapper.setAttribute( 'role', 'region' );
|
3161
|
+
widget.wrapper.setAttribute( 'aria-label', label );
|
3162
|
+
}, null, null, 9999 );
|
3163
|
+
}
|
3164
|
+
|
3165
|
+
function setupDragHandler( widget ) {
|
3166
|
+
if ( !widget.draggable )
|
3167
|
+
return;
|
3168
|
+
|
3169
|
+
var editor = widget.editor,
|
3170
|
+
// Use getLast to find wrapper's direct descendant (http://dev.ckeditor.com/ticket/12022).
|
3171
|
+
container = widget.wrapper.getLast( Widget.isDomDragHandlerContainer ),
|
3172
|
+
img;
|
3173
|
+
|
3174
|
+
// Reuse drag handler if already exists (http://dev.ckeditor.com/ticket/11281).
|
3175
|
+
if ( container )
|
3176
|
+
img = container.findOne( 'img' );
|
3177
|
+
else {
|
3178
|
+
container = new CKEDITOR.dom.element( 'span', editor.document );
|
3179
|
+
container.setAttributes( {
|
3180
|
+
'class': 'cke_reset cke_widget_drag_handler_container',
|
3181
|
+
// Split background and background-image for IE8 which will break on rgba().
|
3182
|
+
style: 'background:rgba(220,220,220,0.5);background-image:url(' + editor.plugins.widget.path + 'images/handle.png)'
|
3183
|
+
} );
|
3184
|
+
|
3185
|
+
img = new CKEDITOR.dom.element( 'img', editor.document );
|
3186
|
+
img.setAttributes( {
|
3187
|
+
'class': 'cke_reset cke_widget_drag_handler',
|
3188
|
+
'data-cke-widget-drag-handler': '1',
|
3189
|
+
src: CKEDITOR.tools.transparentImageData,
|
3190
|
+
width: DRAG_HANDLER_SIZE,
|
3191
|
+
title: editor.lang.widget.move,
|
3192
|
+
height: DRAG_HANDLER_SIZE,
|
3193
|
+
role: 'presentation'
|
3194
|
+
} );
|
3195
|
+
widget.inline && img.setAttribute( 'draggable', 'true' );
|
3196
|
+
|
3197
|
+
container.append( img );
|
3198
|
+
widget.wrapper.append( container );
|
3199
|
+
}
|
3200
|
+
|
3201
|
+
// Preventing page reload when dropped content on widget wrapper (http://dev.ckeditor.com/ticket/13015).
|
3202
|
+
// Widget is not editable so by default drop on it isn't allowed what means that
|
3203
|
+
// browser handles it (there's no editable#drop event). If there's no drop event we cannot block
|
3204
|
+
// the drop, so page is reloaded. This listener enables drop on widget wrappers.
|
3205
|
+
widget.wrapper.on( 'dragover', function( evt ) {
|
3206
|
+
evt.data.preventDefault();
|
3207
|
+
} );
|
3208
|
+
|
3209
|
+
widget.wrapper.on( 'mouseenter', widget.updateDragHandlerPosition, widget );
|
3210
|
+
setTimeout( function() {
|
3211
|
+
widget.on( 'data', widget.updateDragHandlerPosition, widget );
|
3212
|
+
}, 50 );
|
3213
|
+
|
3214
|
+
if ( !widget.inline ) {
|
3215
|
+
img.on( 'mousedown', onBlockWidgetDrag, widget );
|
3216
|
+
|
3217
|
+
// On IE8 'dragstart' is propagated to editable, so editor#dragstart is fired twice on block widgets.
|
3218
|
+
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
|
3219
|
+
img.on( 'dragstart', function( evt ) {
|
3220
|
+
evt.data.preventDefault( true );
|
3221
|
+
} );
|
3222
|
+
}
|
3223
|
+
}
|
3224
|
+
|
3225
|
+
widget.dragHandlerContainer = container;
|
3226
|
+
}
|
3227
|
+
|
3228
|
+
function onBlockWidgetDrag( evt ) {
|
3229
|
+
var finder = this.repository.finder,
|
3230
|
+
locator = this.repository.locator,
|
3231
|
+
liner = this.repository.liner,
|
3232
|
+
editor = this.editor,
|
3233
|
+
editable = editor.editable(),
|
3234
|
+
listeners = [],
|
3235
|
+
sorted = [],
|
3236
|
+
locations,
|
3237
|
+
y;
|
3238
|
+
|
3239
|
+
// Mark dragged widget for repository#finder.
|
3240
|
+
this.repository._.draggedWidget = this;
|
3241
|
+
|
3242
|
+
// Harvest all possible relations and display some closest.
|
3243
|
+
var relations = finder.greedySearch(),
|
3244
|
+
|
3245
|
+
buffer = CKEDITOR.tools.eventsBuffer( 50, function() {
|
3246
|
+
locations = locator.locate( relations );
|
3247
|
+
|
3248
|
+
// There's only a single line displayed for D&D.
|
3249
|
+
sorted = locator.sort( y, 1 );
|
3250
|
+
|
3251
|
+
if ( sorted.length ) {
|
3252
|
+
liner.prepare( relations, locations );
|
3253
|
+
liner.placeLine( sorted[ 0 ] );
|
3254
|
+
liner.cleanup();
|
3255
|
+
}
|
3256
|
+
} );
|
3257
|
+
|
3258
|
+
// Let's have the "dragging cursor" over entire editable.
|
3259
|
+
editable.addClass( 'cke_widget_dragging' );
|
3260
|
+
|
3261
|
+
// Cache mouse position so it is re-used in events buffer.
|
3262
|
+
listeners.push( editable.on( 'mousemove', function( evt ) {
|
3263
|
+
y = evt.data.$.clientY;
|
3264
|
+
buffer.input();
|
3265
|
+
} ) );
|
3266
|
+
|
3267
|
+
// Fire drag start as it happens during the native D&D.
|
3268
|
+
editor.fire( 'dragstart', { target: evt.sender } );
|
3269
|
+
|
3270
|
+
function onMouseUp() {
|
3271
|
+
var l;
|
3272
|
+
|
3273
|
+
buffer.reset();
|
3274
|
+
|
3275
|
+
// Stop observing events.
|
3276
|
+
while ( ( l = listeners.pop() ) )
|
3277
|
+
l.removeListener();
|
3278
|
+
|
3279
|
+
onBlockWidgetDrop.call( this, sorted, evt.sender );
|
3280
|
+
}
|
3281
|
+
|
3282
|
+
// Mouseup means "drop". This is when the widget is being detached
|
3283
|
+
// from DOM and placed at range determined by the line (location).
|
3284
|
+
listeners.push( editor.document.once( 'mouseup', onMouseUp, this ) );
|
3285
|
+
|
3286
|
+
// Prevent calling 'onBlockWidgetDrop' twice in the inline editor.
|
3287
|
+
// `removeListener` does not work if it is called at the same time event is fired.
|
3288
|
+
if ( !editable.isInline() ) {
|
3289
|
+
// Mouseup may occur when user hovers the line, which belongs to
|
3290
|
+
// the outer document. This is, of course, a valid listener too.
|
3291
|
+
listeners.push( CKEDITOR.document.once( 'mouseup', onMouseUp, this ) );
|
3292
|
+
}
|
3293
|
+
}
|
3294
|
+
|
3295
|
+
function onBlockWidgetDrop( sorted, dragTarget ) {
|
3296
|
+
var finder = this.repository.finder,
|
3297
|
+
liner = this.repository.liner,
|
3298
|
+
editor = this.editor,
|
3299
|
+
editable = this.editor.editable();
|
3300
|
+
|
3301
|
+
if ( !CKEDITOR.tools.isEmpty( liner.visible ) ) {
|
3302
|
+
// Retrieve range for the closest location.
|
3303
|
+
var dropRange = finder.getRange( sorted[ 0 ] );
|
3304
|
+
|
3305
|
+
// Focus widget (it could lost focus after mousedown+mouseup)
|
3306
|
+
// and save this state as the one where we want to be taken back when undoing.
|
3307
|
+
this.focus();
|
3308
|
+
|
3309
|
+
// Drag range will be set in the drop listener.
|
3310
|
+
editor.fire( 'drop', {
|
3311
|
+
dropRange: dropRange,
|
3312
|
+
target: dropRange.startContainer
|
3313
|
+
} );
|
3314
|
+
}
|
3315
|
+
|
3316
|
+
// Clean-up custom cursor for editable.
|
3317
|
+
editable.removeClass( 'cke_widget_dragging' );
|
3318
|
+
|
3319
|
+
// Clean-up all remaining lines.
|
3320
|
+
liner.hideVisible();
|
3321
|
+
|
3322
|
+
// Clean-up drag & drop.
|
3323
|
+
editor.fire( 'dragend', { target: dragTarget } );
|
3324
|
+
}
|
3325
|
+
|
3326
|
+
function setupEditables( widget ) {
|
3327
|
+
var editableName,
|
3328
|
+
editableDef,
|
3329
|
+
definedEditables = widget.editables;
|
3330
|
+
|
3331
|
+
widget.editables = {};
|
3332
|
+
|
3333
|
+
if ( !widget.editables )
|
3334
|
+
return;
|
3335
|
+
|
3336
|
+
for ( editableName in definedEditables ) {
|
3337
|
+
editableDef = definedEditables[ editableName ];
|
3338
|
+
widget.initEditable( editableName, typeof editableDef == 'string' ? { selector: editableDef } : editableDef );
|
3339
|
+
}
|
3340
|
+
}
|
3341
|
+
|
3342
|
+
function setupMask( widget ) {
|
3343
|
+
if ( !widget.mask )
|
3344
|
+
return;
|
3345
|
+
|
3346
|
+
// Reuse mask if already exists (http://dev.ckeditor.com/ticket/11281).
|
3347
|
+
var img = widget.wrapper.findOne( '.cke_widget_mask' );
|
3348
|
+
|
3349
|
+
if ( !img ) {
|
3350
|
+
img = new CKEDITOR.dom.element( 'img', widget.editor.document );
|
3351
|
+
img.setAttributes( {
|
3352
|
+
src: CKEDITOR.tools.transparentImageData,
|
3353
|
+
'class': 'cke_reset cke_widget_mask'
|
3354
|
+
} );
|
3355
|
+
widget.wrapper.append( img );
|
3356
|
+
}
|
3357
|
+
|
3358
|
+
widget.mask = img;
|
3359
|
+
}
|
3360
|
+
|
3361
|
+
// Replace parts object containing:
|
3362
|
+
// partName => selector pairs
|
3363
|
+
// with:
|
3364
|
+
// partName => element pairs
|
3365
|
+
function setupParts( widget ) {
|
3366
|
+
if ( widget.parts ) {
|
3367
|
+
var parts = {},
|
3368
|
+
el, partName;
|
3369
|
+
|
3370
|
+
for ( partName in widget.parts ) {
|
3371
|
+
el = widget.wrapper.findOne( widget.parts[ partName ] );
|
3372
|
+
parts[ partName ] = el;
|
3373
|
+
}
|
3374
|
+
widget.parts = parts;
|
3375
|
+
}
|
3376
|
+
}
|
3377
|
+
|
3378
|
+
function setupWidget( widget, widgetDef ) {
|
3379
|
+
setupWrapper( widget );
|
3380
|
+
setupParts( widget );
|
3381
|
+
setupEditables( widget );
|
3382
|
+
setupMask( widget );
|
3383
|
+
setupDragHandler( widget );
|
3384
|
+
setupDataClassesListener( widget );
|
3385
|
+
setupA11yListener( widget );
|
3386
|
+
|
3387
|
+
// http://dev.ckeditor.com/ticket/11145: [IE8] Non-editable content of widget is draggable.
|
3388
|
+
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
|
3389
|
+
widget.wrapper.on( 'dragstart', function( evt ) {
|
3390
|
+
var target = evt.data.getTarget();
|
3391
|
+
|
3392
|
+
// Allow text dragging inside nested editables or dragging inline widget's drag handler.
|
3393
|
+
if ( !Widget.getNestedEditable( widget, target ) && !( widget.inline && Widget.isDomDragHandler( target ) ) )
|
3394
|
+
evt.data.preventDefault();
|
3395
|
+
} );
|
3396
|
+
}
|
3397
|
+
|
3398
|
+
widget.wrapper.removeClass( 'cke_widget_new' );
|
3399
|
+
widget.element.addClass( 'cke_widget_element' );
|
3400
|
+
|
3401
|
+
widget.on( 'key', function( evt ) {
|
3402
|
+
var keyCode = evt.data.keyCode;
|
3403
|
+
|
3404
|
+
// ENTER.
|
3405
|
+
if ( keyCode == 13 ) {
|
3406
|
+
widget.edit();
|
3407
|
+
// CTRL+C or CTRL+X.
|
3408
|
+
} else if ( keyCode == CKEDITOR.CTRL + 67 || keyCode == CKEDITOR.CTRL + 88 ) {
|
3409
|
+
copySingleWidget( widget, keyCode == CKEDITOR.CTRL + 88 );
|
3410
|
+
return; // Do not preventDefault.
|
3411
|
+
} else if ( keyCode in keystrokesNotBlockedByWidget || ( CKEDITOR.CTRL & keyCode ) || ( CKEDITOR.ALT & keyCode ) ) {
|
3412
|
+
// Pass chosen keystrokes to other plugins or default fake sel handlers.
|
3413
|
+
// Pass all CTRL/ALT keystrokes.
|
3414
|
+
return;
|
3415
|
+
}
|
3416
|
+
|
3417
|
+
return false;
|
3418
|
+
}, null, null, 999 );
|
3419
|
+
// Listen with high priority so it's possible
|
3420
|
+
// to overwrite this callback.
|
3421
|
+
|
3422
|
+
widget.on( 'doubleclick', function( evt ) {
|
3423
|
+
if ( widget.edit() ) {
|
3424
|
+
// We have to cancel event if edit method opens a dialog, otherwise
|
3425
|
+
// link plugin may open extra dialog (http://dev.ckeditor.com/ticket/12140).
|
3426
|
+
evt.cancel();
|
3427
|
+
}
|
3428
|
+
} );
|
3429
|
+
|
3430
|
+
if ( widgetDef.data )
|
3431
|
+
widget.on( 'data', widgetDef.data );
|
3432
|
+
|
3433
|
+
if ( widgetDef.edit )
|
3434
|
+
widget.on( 'edit', widgetDef.edit );
|
3435
|
+
}
|
3436
|
+
|
3437
|
+
function setupWidgetData( widget, startupData ) {
|
3438
|
+
var widgetDataAttr = widget.element.data( 'cke-widget-data' );
|
3439
|
+
|
3440
|
+
if ( widgetDataAttr )
|
3441
|
+
widget.setData( JSON.parse( decodeURIComponent( widgetDataAttr ) ) );
|
3442
|
+
if ( startupData )
|
3443
|
+
widget.setData( startupData );
|
3444
|
+
|
3445
|
+
// Populate classes if they are not preset.
|
3446
|
+
if ( !widget.data.classes )
|
3447
|
+
widget.setData( 'classes', widget.getClasses() );
|
3448
|
+
|
3449
|
+
// Unblock data and...
|
3450
|
+
widget.dataReady = true;
|
3451
|
+
|
3452
|
+
// Write data to element because this was blocked when data wasn't ready.
|
3453
|
+
writeDataToElement( widget );
|
3454
|
+
|
3455
|
+
// Fire data event first time, because this was blocked when data wasn't ready.
|
3456
|
+
widget.fire( 'data', widget.data );
|
3457
|
+
}
|
3458
|
+
|
3459
|
+
function setupWrapper( widget ) {
|
3460
|
+
// Retrieve widget wrapper. Assign an id to it.
|
3461
|
+
var wrapper = widget.wrapper = widget.element.getParent();
|
3462
|
+
wrapper.setAttribute( 'data-cke-widget-id', widget.id );
|
3463
|
+
}
|
3464
|
+
|
3465
|
+
function writeDataToElement( widget ) {
|
3466
|
+
widget.element.data( 'cke-widget-data', encodeURIComponent( JSON.stringify( widget.data ) ) );
|
3467
|
+
}
|
3468
|
+
|
3469
|
+
//
|
3470
|
+
// WIDGET STYLE HANDLER ---------------------------------------------------
|
3471
|
+
//
|
3472
|
+
|
3473
|
+
( function() {
|
3474
|
+
// Styles categorized by group. It is used to prevent applying styles for the same group being used together.
|
3475
|
+
var styleGroups = {};
|
3476
|
+
|
3477
|
+
/**
|
3478
|
+
* The class representing a widget style. It is an {@link CKEDITOR#STYLE_OBJECT object} like
|
3479
|
+
* the styles handler for widgets.
|
3480
|
+
*
|
3481
|
+
* **Note:** This custom style handler does not support all methods of the {@link CKEDITOR.style} class.
|
3482
|
+
* Not supported methods: {@link #applyToRange}, {@link #removeFromRange}, {@link #applyToObject}.
|
3483
|
+
*
|
3484
|
+
* @since 4.4
|
3485
|
+
* @class CKEDITOR.style.customHandlers.widget
|
3486
|
+
* @extends CKEDITOR.style
|
3487
|
+
*/
|
3488
|
+
CKEDITOR.style.addCustomHandler( {
|
3489
|
+
type: 'widget',
|
3490
|
+
|
3491
|
+
setup: function( styleDefinition ) {
|
3492
|
+
/**
|
3493
|
+
* The name of widget to which this style can be applied.
|
3494
|
+
* It is extracted from style definition's `widget` property.
|
3495
|
+
*
|
3496
|
+
* @property {String} widget
|
3497
|
+
*/
|
3498
|
+
this.widget = styleDefinition.widget;
|
3499
|
+
|
3500
|
+
/**
|
3501
|
+
* An array of groups that this style belongs to.
|
3502
|
+
* Styles assigned to the same group cannot be combined.
|
3503
|
+
*
|
3504
|
+
* @since 4.6.2
|
3505
|
+
* @property {Array} group
|
3506
|
+
*/
|
3507
|
+
this.group = typeof styleDefinition.group == 'string' ? [ styleDefinition.group ] : styleDefinition.group;
|
3508
|
+
|
3509
|
+
// Store style categorized by its group.
|
3510
|
+
// It is used to prevent enabling two styles from same group.
|
3511
|
+
if ( this.group ) {
|
3512
|
+
saveStyleGroup( this );
|
3513
|
+
}
|
3514
|
+
},
|
3515
|
+
|
3516
|
+
apply: function( editor ) {
|
3517
|
+
var widget;
|
3518
|
+
|
3519
|
+
// Before CKEditor 4.4 wasn't a required argument, so we need to
|
3520
|
+
// handle a case when it wasn't provided.
|
3521
|
+
if ( !( editor instanceof CKEDITOR.editor ) )
|
3522
|
+
return;
|
3523
|
+
|
3524
|
+
// Theoretically we could bypass checkApplicable, get widget from
|
3525
|
+
// widgets.focused and check its name, what would be faster, but then
|
3526
|
+
// this custom style would work differently than the default style
|
3527
|
+
// which checks if it's applicable before applying or removing itself.
|
3528
|
+
if ( this.checkApplicable( editor.elementPath(), editor ) ) {
|
3529
|
+
widget = editor.widgets.focused;
|
3530
|
+
|
3531
|
+
// Remove other styles from the same group.
|
3532
|
+
if ( this.group ) {
|
3533
|
+
this.removeStylesFromSameGroup( editor );
|
3534
|
+
}
|
3535
|
+
|
3536
|
+
widget.applyStyle( this );
|
3537
|
+
}
|
3538
|
+
},
|
3539
|
+
|
3540
|
+
remove: function( editor ) {
|
3541
|
+
// Before CKEditor 4.4 wasn't a required argument, so we need to
|
3542
|
+
// handle a case when it wasn't provided.
|
3543
|
+
if ( !( editor instanceof CKEDITOR.editor ) )
|
3544
|
+
return;
|
3545
|
+
|
3546
|
+
if ( this.checkApplicable( editor.elementPath(), editor ) )
|
3547
|
+
editor.widgets.focused.removeStyle( this );
|
3548
|
+
},
|
3549
|
+
|
3550
|
+
/**
|
3551
|
+
* Removes all styles that belong to the same group as this style. This method will neither add nor remove
|
3552
|
+
* the current style.
|
3553
|
+
* Returns `true` if any style was removed, otherwise returns `false`.
|
3554
|
+
*
|
3555
|
+
* @since 4.6.2
|
3556
|
+
* @param {CKEDITOR.editor} editor
|
3557
|
+
* @returns {Boolean}
|
3558
|
+
*/
|
3559
|
+
removeStylesFromSameGroup: function( editor ) {
|
3560
|
+
var stylesFromSameGroup,
|
3561
|
+
path,
|
3562
|
+
removed = false;
|
3563
|
+
|
3564
|
+
// Before CKEditor 4.4 wasn't a required argument, so we need to
|
3565
|
+
// handle a case when it wasn't provided.
|
3566
|
+
if ( !( editor instanceof CKEDITOR.editor ) )
|
3567
|
+
return false;
|
3568
|
+
|
3569
|
+
path = editor.elementPath();
|
3570
|
+
if ( this.checkApplicable( path, editor ) ) {
|
3571
|
+
// Iterate over each group.
|
3572
|
+
for ( var i = 0, l = this.group.length; i < l; i++ ) {
|
3573
|
+
stylesFromSameGroup = styleGroups[ this.widget ][ this.group[ i ] ];
|
3574
|
+
// Iterate over each style from group.
|
3575
|
+
for ( var j = 0; j < stylesFromSameGroup.length; j++ ) {
|
3576
|
+
if ( stylesFromSameGroup[ j ] !== this && stylesFromSameGroup[ j ].checkActive( path, editor ) ) {
|
3577
|
+
editor.widgets.focused.removeStyle( stylesFromSameGroup[ j ] );
|
3578
|
+
removed = true;
|
3579
|
+
}
|
3580
|
+
}
|
3581
|
+
}
|
3582
|
+
}
|
3583
|
+
|
3584
|
+
return removed;
|
3585
|
+
},
|
3586
|
+
|
3587
|
+
checkActive: function( elementPath, editor ) {
|
3588
|
+
return this.checkElementMatch( elementPath.lastElement, 0, editor );
|
3589
|
+
},
|
3590
|
+
|
3591
|
+
checkApplicable: function( elementPath, editor ) {
|
3592
|
+
// Before CKEditor 4.4 wasn't a required argument, so we need to
|
3593
|
+
// handle a case when it wasn't provided.
|
3594
|
+
if ( !( editor instanceof CKEDITOR.editor ) )
|
3595
|
+
return false;
|
3596
|
+
|
3597
|
+
return this.checkElement( elementPath.lastElement );
|
3598
|
+
},
|
3599
|
+
|
3600
|
+
checkElementMatch: checkElementMatch,
|
3601
|
+
|
3602
|
+
checkElementRemovable: checkElementMatch,
|
3603
|
+
|
3604
|
+
/**
|
3605
|
+
* Checks if an element is a {@link CKEDITOR.plugins.widget#wrapper wrapper} of a
|
3606
|
+
* widget whose name matches the {@link #widget widget name} specified in the style definition.
|
3607
|
+
*
|
3608
|
+
* @param {CKEDITOR.dom.element} element
|
3609
|
+
* @returns {Boolean}
|
3610
|
+
*/
|
3611
|
+
checkElement: function( element ) {
|
3612
|
+
if ( !Widget.isDomWidgetWrapper( element ) )
|
3613
|
+
return false;
|
3614
|
+
|
3615
|
+
var widgetElement = element.getFirst( Widget.isDomWidgetElement );
|
3616
|
+
return widgetElement && widgetElement.data( 'widget' ) == this.widget;
|
3617
|
+
},
|
3618
|
+
|
3619
|
+
buildPreview: function( label ) {
|
3620
|
+
return label || this._.definition.name;
|
3621
|
+
},
|
3622
|
+
|
3623
|
+
/**
|
3624
|
+
* Returns allowed content rules which should be registered for this style.
|
3625
|
+
* Uses widget's {@link CKEDITOR.plugins.widget.definition#styleableElements} to make a rule
|
3626
|
+
* allowing classes on specified elements or use widget's
|
3627
|
+
* {@link CKEDITOR.plugins.widget.definition#styleToAllowedContentRules} method to transform a style
|
3628
|
+
* into allowed content rules.
|
3629
|
+
*
|
3630
|
+
* @param {CKEDITOR.editor} The editor instance.
|
3631
|
+
* @returns {CKEDITOR.filter.allowedContentRules}
|
3632
|
+
*/
|
3633
|
+
toAllowedContentRules: function( editor ) {
|
3634
|
+
if ( !editor )
|
3635
|
+
return null;
|
3636
|
+
|
3637
|
+
var widgetDef = editor.widgets.registered[ this.widget ],
|
3638
|
+
classes,
|
3639
|
+
rule = {};
|
3640
|
+
|
3641
|
+
if ( !widgetDef )
|
3642
|
+
return null;
|
3643
|
+
|
3644
|
+
if ( widgetDef.styleableElements ) {
|
3645
|
+
classes = this.getClassesArray();
|
3646
|
+
if ( !classes )
|
3647
|
+
return null;
|
3648
|
+
|
3649
|
+
rule[ widgetDef.styleableElements ] = {
|
3650
|
+
classes: classes,
|
3651
|
+
propertiesOnly: true
|
3652
|
+
};
|
3653
|
+
return rule;
|
3654
|
+
}
|
3655
|
+
if ( widgetDef.styleToAllowedContentRules )
|
3656
|
+
return widgetDef.styleToAllowedContentRules( this );
|
3657
|
+
return null;
|
3658
|
+
},
|
3659
|
+
|
3660
|
+
/**
|
3661
|
+
* Returns classes defined in the style in form of an array.
|
3662
|
+
*
|
3663
|
+
* @returns {String[]}
|
3664
|
+
*/
|
3665
|
+
getClassesArray: function() {
|
3666
|
+
var classes = this._.definition.attributes && this._.definition.attributes[ 'class' ];
|
3667
|
+
|
3668
|
+
return classes ? CKEDITOR.tools.trim( classes ).split( /\s+/ ) : null;
|
3669
|
+
},
|
3670
|
+
|
3671
|
+
/**
|
3672
|
+
* Not implemented.
|
3673
|
+
*
|
3674
|
+
* @method applyToRange
|
3675
|
+
*/
|
3676
|
+
applyToRange: notImplemented,
|
3677
|
+
|
3678
|
+
/**
|
3679
|
+
* Not implemented.
|
3680
|
+
*
|
3681
|
+
* @method removeFromRange
|
3682
|
+
*/
|
3683
|
+
removeFromRange: notImplemented,
|
3684
|
+
|
3685
|
+
/**
|
3686
|
+
* Not implemented.
|
3687
|
+
*
|
3688
|
+
* @method applyToObject
|
3689
|
+
*/
|
3690
|
+
applyToObject: notImplemented
|
3691
|
+
} );
|
3692
|
+
|
3693
|
+
function notImplemented() {}
|
3694
|
+
|
3695
|
+
// @context style
|
3696
|
+
function checkElementMatch( element, fullMatch, editor ) {
|
3697
|
+
// Before CKEditor 4.4 wasn't a required argument, so we need to
|
3698
|
+
// handle a case when it wasn't provided.
|
3699
|
+
if ( !editor )
|
3700
|
+
return false;
|
3701
|
+
|
3702
|
+
if ( !this.checkElement( element ) )
|
3703
|
+
return false;
|
3704
|
+
|
3705
|
+
var widget = editor.widgets.getByElement( element, true );
|
3706
|
+
return widget && widget.checkStyleActive( this );
|
3707
|
+
}
|
3708
|
+
|
3709
|
+
// Save and categorize style by its group.
|
3710
|
+
function saveStyleGroup( style ) {
|
3711
|
+
var widgetName = style.widget,
|
3712
|
+
group;
|
3713
|
+
|
3714
|
+
if ( !styleGroups[ widgetName ] ) {
|
3715
|
+
styleGroups[ widgetName ] = {};
|
3716
|
+
}
|
3717
|
+
|
3718
|
+
for ( var i = 0, l = style.group.length; i < l; i++ ) {
|
3719
|
+
group = style.group[ i ];
|
3720
|
+
if ( !styleGroups[ widgetName ][ group ] ) {
|
3721
|
+
styleGroups[ widgetName ][ group ] = [];
|
3722
|
+
}
|
3723
|
+
|
3724
|
+
styleGroups[ widgetName ][ group ].push( style );
|
3725
|
+
}
|
3726
|
+
}
|
3727
|
+
|
3728
|
+
} )();
|
3729
|
+
|
3730
|
+
//
|
3731
|
+
// EXPOSE PUBLIC API ------------------------------------------------------
|
3732
|
+
//
|
3733
|
+
|
3734
|
+
CKEDITOR.plugins.widget = Widget;
|
3735
|
+
Widget.repository = Repository;
|
3736
|
+
Widget.nestedEditable = NestedEditable;
|
3737
|
+
} )();
|
3738
|
+
|
3739
|
+
/**
|
3740
|
+
* An event fired when a widget definition is registered by the {@link CKEDITOR.plugins.widget.repository#add} method.
|
3741
|
+
* It is possible to modify the definition being registered.
|
3742
|
+
*
|
3743
|
+
* @event widgetDefinition
|
3744
|
+
* @member CKEDITOR.editor
|
3745
|
+
* @param {CKEDITOR.plugins.widget.definition} data Widget definition.
|
3746
|
+
*/
|
3747
|
+
|
3748
|
+
/**
|
3749
|
+
* This is an abstract class that describes the definition of a widget.
|
3750
|
+
* It is a type of {@link CKEDITOR.plugins.widget.repository#add} method's second argument.
|
3751
|
+
*
|
3752
|
+
* Widget instances inherit from registered widget definitions, although not in a prototypal way.
|
3753
|
+
* They are simply extended with corresponding widget definitions. Note that not all properties of
|
3754
|
+
* the widget definition become properties of a widget. Some, like {@link #data} or {@link #edit}, become
|
3755
|
+
* widget's events listeners.
|
3756
|
+
*
|
3757
|
+
* @class CKEDITOR.plugins.widget.definition
|
3758
|
+
* @abstract
|
3759
|
+
* @mixins CKEDITOR.feature
|
3760
|
+
*/
|
3761
|
+
|
3762
|
+
/**
|
3763
|
+
* Widget definition name. It is automatically set when the definition is
|
3764
|
+
* {@link CKEDITOR.plugins.widget.repository#add registered}.
|
3765
|
+
*
|
3766
|
+
* @property {String} name
|
3767
|
+
*/
|
3768
|
+
|
3769
|
+
/**
|
3770
|
+
* The method executed while initializing a widget, after a widget instance
|
3771
|
+
* is created, but before it is ready. It is executed before the first
|
3772
|
+
* {@link CKEDITOR.plugins.widget#event-data} is fired so it is common to
|
3773
|
+
* use the `init` method to populate widget data with information loaded from
|
3774
|
+
* the DOM, like for exmaple:
|
3775
|
+
*
|
3776
|
+
* init: function() {
|
3777
|
+
* this.setData( 'width', this.element.getStyle( 'width' ) );
|
3778
|
+
*
|
3779
|
+
* if ( this.parts.caption.getStyle( 'display' ) != 'none' )
|
3780
|
+
* this.setData( 'showCaption', true );
|
3781
|
+
* }
|
3782
|
+
*
|
3783
|
+
* @property {Function} init
|
3784
|
+
*/
|
3785
|
+
|
3786
|
+
/**
|
3787
|
+
* The function to be used to upcast an element to this widget or a
|
3788
|
+
* comma-separated list of upcast methods from the {@link #upcasts} object.
|
3789
|
+
*
|
3790
|
+
* The upcast function **is not** executed in the widget context (because the widget
|
3791
|
+
* does not exist yet) and two arguments are passed:
|
3792
|
+
*
|
3793
|
+
* * `element` ({@link CKEDITOR.htmlParser.element}) – The element to be checked.
|
3794
|
+
* * `data` (`Object`) – The object which can be extended with data which will then be passed to the widget.
|
3795
|
+
*
|
3796
|
+
* An element will be upcasted if a function returned `true` or an instance of
|
3797
|
+
* a {@link CKEDITOR.htmlParser.element} if upcasting meant DOM structure changes
|
3798
|
+
* (in this case the widget will be initialized on the returned element).
|
3799
|
+
*
|
3800
|
+
* @property {String/Function} upcast
|
3801
|
+
*/
|
3802
|
+
|
3803
|
+
/**
|
3804
|
+
* The object containing functions which can be used to upcast this widget.
|
3805
|
+
* Only those pointed by the {@link #upcast} property will be used.
|
3806
|
+
*
|
3807
|
+
* In most cases it is appropriate to use {@link #upcast} directly,
|
3808
|
+
* because majority of widgets need just one method.
|
3809
|
+
* However, in some cases the widget author may want to expose more than one variant
|
3810
|
+
* and then this property may be used.
|
3811
|
+
*
|
3812
|
+
* upcasts: {
|
3813
|
+
* // This function may upcast only figure elements.
|
3814
|
+
* figure: function() {
|
3815
|
+
* // ...
|
3816
|
+
* },
|
3817
|
+
* // This function may upcast only image elements.
|
3818
|
+
* image: function() {
|
3819
|
+
* // ...
|
3820
|
+
* },
|
3821
|
+
* // More variants...
|
3822
|
+
* }
|
3823
|
+
*
|
3824
|
+
* // Then, widget user may choose which upcast methods will be enabled.
|
3825
|
+
* editor.on( 'widgetDefinition', function( evt ) {
|
3826
|
+
* if ( evt.data.name == 'image' )
|
3827
|
+
* evt.data.upcast = 'figure,image'; // Use both methods.
|
3828
|
+
* } );
|
3829
|
+
*
|
3830
|
+
* @property {Object} upcasts
|
3831
|
+
*/
|
3832
|
+
|
3833
|
+
/**
|
3834
|
+
* The {@link #upcast} method(s) priority. The upcast with a lower priority number will be called before
|
3835
|
+
* the one with a higher number. The default priority is `10`.
|
3836
|
+
*
|
3837
|
+
* @since 4.5
|
3838
|
+
* @property {Number} [upcastPriority=10]
|
3839
|
+
*/
|
3840
|
+
|
3841
|
+
/**
|
3842
|
+
* The function to be used to downcast this widget or
|
3843
|
+
* a name of the downcast option from the {@link #downcasts} object.
|
3844
|
+
*
|
3845
|
+
* The downcast funciton will be executed in the {@link CKEDITOR.plugins.widget} context
|
3846
|
+
* and with `widgetElement` ({@link CKEDITOR.htmlParser.element}) argument which is
|
3847
|
+
* the widget's main element.
|
3848
|
+
*
|
3849
|
+
* The function may return an instance of the {@link CKEDITOR.htmlParser.node} class if the widget
|
3850
|
+
* needs to be downcasted to a different node than the widget's main element.
|
3851
|
+
*
|
3852
|
+
* @property {String/Function} downcast
|
3853
|
+
*/
|
3854
|
+
|
3855
|
+
/**
|
3856
|
+
* The object containing functions which can be used to downcast this widget.
|
3857
|
+
* Only the one pointed by the {@link #downcast} property will be used.
|
3858
|
+
*
|
3859
|
+
* In most cases it is appropriate to use {@link #downcast} directly,
|
3860
|
+
* because majority of widgets have just one variant of downcasting (or none at all).
|
3861
|
+
* However, in some cases the widget author may want to expose more than one variant
|
3862
|
+
* and then this property may be used.
|
3863
|
+
*
|
3864
|
+
* downcasts: {
|
3865
|
+
* // This downcast may transform the widget into the figure element.
|
3866
|
+
* figure: function() {
|
3867
|
+
* // ...
|
3868
|
+
* },
|
3869
|
+
* // This downcast may transform the widget into the image element with data-* attributes.
|
3870
|
+
* image: function() {
|
3871
|
+
* // ...
|
3872
|
+
* }
|
3873
|
+
* }
|
3874
|
+
*
|
3875
|
+
* // Then, the widget user may choose one of the downcast options when setting up his editor.
|
3876
|
+
* editor.on( 'widgetDefinition', function( evt ) {
|
3877
|
+
* if ( evt.data.name == 'image' )
|
3878
|
+
* evt.data.downcast = 'figure';
|
3879
|
+
* } );
|
3880
|
+
*
|
3881
|
+
* @property downcasts
|
3882
|
+
*/
|
3883
|
+
|
3884
|
+
/**
|
3885
|
+
* If set, it will be added as the {@link CKEDITOR.plugins.widget#event-edit} event listener.
|
3886
|
+
* This means that it will be executed when a widget is being edited.
|
3887
|
+
* See the {@link CKEDITOR.plugins.widget#method-edit} method.
|
3888
|
+
*
|
3889
|
+
* @property {Function} edit
|
3890
|
+
*/
|
3891
|
+
|
3892
|
+
/**
|
3893
|
+
* If set, it will be added as the {@link CKEDITOR.plugins.widget#event-data} event listener.
|
3894
|
+
* This means that it will be executed every time the {@link CKEDITOR.plugins.widget#property-data widget data} changes.
|
3895
|
+
*
|
3896
|
+
* @property {Function} data
|
3897
|
+
*/
|
3898
|
+
|
3899
|
+
/**
|
3900
|
+
* The method to be executed when the widget's command is executed in order to insert a new widget
|
3901
|
+
* (widget of this type is not focused). If not defined, then the default action will be
|
3902
|
+
* performed which means that:
|
3903
|
+
*
|
3904
|
+
* * An instance of the widget will be created in a detached {@link CKEDITOR.dom.documentFragment document fragment},
|
3905
|
+
* * The {@link CKEDITOR.plugins.widget#method-edit} method will be called to trigger widget editing,
|
3906
|
+
* * The widget element will be inserted into DOM.
|
3907
|
+
*
|
3908
|
+
* @property {Function} insert
|
3909
|
+
*/
|
3910
|
+
|
3911
|
+
/**
|
3912
|
+
* The name of a dialog window which will be opened on {@link CKEDITOR.plugins.widget#method-edit}.
|
3913
|
+
* If not defined, then the {@link CKEDITOR.plugins.widget#method-edit} method will not perform any action and
|
3914
|
+
* widget's command will insert a new widget without opening a dialog window first.
|
3915
|
+
*
|
3916
|
+
* @property {String} dialog
|
3917
|
+
*/
|
3918
|
+
|
3919
|
+
/**
|
3920
|
+
* The template which will be used to create a new widget element (when the widget's command is executed).
|
3921
|
+
* This string is populated with {@link #defaults default values} by using the {@link CKEDITOR.template} format.
|
3922
|
+
* Therefore it has to be a valid {@link CKEDITOR.template} argument.
|
3923
|
+
*
|
3924
|
+
* @property {String} template
|
3925
|
+
*/
|
3926
|
+
|
3927
|
+
/**
|
3928
|
+
* The data object which will be used to populate the data of a newly created widget.
|
3929
|
+
* See {@link CKEDITOR.plugins.widget#property-data}.
|
3930
|
+
*
|
3931
|
+
* defaults: {
|
3932
|
+
* showCaption: true,
|
3933
|
+
* align: 'none'
|
3934
|
+
* }
|
3935
|
+
*
|
3936
|
+
* @property defaults
|
3937
|
+
*/
|
3938
|
+
|
3939
|
+
/**
|
3940
|
+
* An object containing definitions of widget components (part name => CSS selector).
|
3941
|
+
*
|
3942
|
+
* parts: {
|
3943
|
+
* image: 'img',
|
3944
|
+
* caption: 'div.caption'
|
3945
|
+
* }
|
3946
|
+
*
|
3947
|
+
* @property parts
|
3948
|
+
*/
|
3949
|
+
|
3950
|
+
/**
|
3951
|
+
* An object containing definitions of nested editables (editable name => {@link CKEDITOR.plugins.widget.nestedEditable.definition}).
|
3952
|
+
* Note that editables *have to* be defined in the same order as they are in DOM / {@link CKEDITOR.plugins.widget.definition#template template}.
|
3953
|
+
* Otherwise errors will occur when nesting widgets inside each other.
|
3954
|
+
*
|
3955
|
+
* editables: {
|
3956
|
+
* header: 'h1',
|
3957
|
+
* content: {
|
3958
|
+
* selector: 'div.content',
|
3959
|
+
* allowedContent: 'p strong em; a[!href]'
|
3960
|
+
* }
|
3961
|
+
* }
|
3962
|
+
*
|
3963
|
+
* @property editables
|
3964
|
+
*/
|
3965
|
+
|
3966
|
+
/**
|
3967
|
+
* The function used to obtain an accessibility label for the widget. It might be used to make
|
3968
|
+
* the widget labels as precise as possible, since it has access to the widget instance.
|
3969
|
+
*
|
3970
|
+
* If not specified, the default implementation will use the {@link #pathName} or the main
|
3971
|
+
* {@link CKEDITOR.plugins.widget#element element} tag name.
|
3972
|
+
*
|
3973
|
+
* @property {Function} getLabel
|
3974
|
+
*/
|
3975
|
+
|
3976
|
+
/**
|
3977
|
+
* The widget name displayed in the elements path.
|
3978
|
+
*
|
3979
|
+
* @property {String} pathName
|
3980
|
+
*/
|
3981
|
+
|
3982
|
+
/**
|
3983
|
+
* If set to `true`, the widget's element will be covered with a transparent mask.
|
3984
|
+
* This will prevent its content from being clickable, which matters in case
|
3985
|
+
* of special elements like embedded Flash or iframes that generate a separate "context".
|
3986
|
+
*
|
3987
|
+
* @property {Boolean} mask
|
3988
|
+
*/
|
3989
|
+
|
3990
|
+
/**
|
3991
|
+
* If set to `true/false`, it will force the widget to be either an inline or a block widget.
|
3992
|
+
* If not set, the widget type will be determined from the widget element.
|
3993
|
+
*
|
3994
|
+
* Widget type influences whether a block (`div`) or an inline (`span`) element is used
|
3995
|
+
* for the wrapper.
|
3996
|
+
*
|
3997
|
+
* @property {Boolean} inline
|
3998
|
+
*/
|
3999
|
+
|
4000
|
+
/**
|
4001
|
+
* The label for the widget toolbar button.
|
4002
|
+
*
|
4003
|
+
* editor.widgets.add( 'simplebox', {
|
4004
|
+
* button: 'Create a simple box'
|
4005
|
+
* } );
|
4006
|
+
*
|
4007
|
+
* editor.widgets.add( 'simplebox', {
|
4008
|
+
* button: editor.lang.simplebox.title
|
4009
|
+
* } );
|
4010
|
+
*
|
4011
|
+
* @property {String} button
|
4012
|
+
*/
|
4013
|
+
|
4014
|
+
/**
|
4015
|
+
* Whether widget should be draggable. Defaults to `true`.
|
4016
|
+
* If set to `false` drag handler will not be displayed when hovering widget.
|
4017
|
+
*
|
4018
|
+
* @property {Boolean} draggable
|
4019
|
+
*/
|
4020
|
+
|
4021
|
+
/**
|
4022
|
+
* Names of element(s) (separated by spaces) for which the {@link CKEDITOR.filter} should allow classes
|
4023
|
+
* defined in the widget styles. For example if your widget is upcasted from a simple `<div>`
|
4024
|
+
* element, then in order to make it styleable you can set:
|
4025
|
+
*
|
4026
|
+
* editor.widgets.add( 'customWidget', {
|
4027
|
+
* upcast: function( element ) {
|
4028
|
+
* return element.name == 'div';
|
4029
|
+
* },
|
4030
|
+
*
|
4031
|
+
* // ...
|
4032
|
+
*
|
4033
|
+
* styleableElements: 'div'
|
4034
|
+
* } );
|
4035
|
+
*
|
4036
|
+
* Then, when the following style is defined:
|
4037
|
+
*
|
4038
|
+
* {
|
4039
|
+
* name: 'Thick border', type: 'widget', widget: 'customWidget',
|
4040
|
+
* attributes: { 'class': 'thickBorder' }
|
4041
|
+
* }
|
4042
|
+
*
|
4043
|
+
* a rule allowing the `thickBorder` class for `div` elements will be registered in the {@link CKEDITOR.filter}.
|
4044
|
+
*
|
4045
|
+
* If you need to have more freedom when transforming widget style to allowed content rules,
|
4046
|
+
* you can use the {@link #styleToAllowedContentRules} callback.
|
4047
|
+
*
|
4048
|
+
* @since 4.4
|
4049
|
+
* @property {String} styleableElements
|
4050
|
+
*/
|
4051
|
+
|
4052
|
+
/**
|
4053
|
+
* Function transforming custom widget's {@link CKEDITOR.style} instance into
|
4054
|
+
* {@link CKEDITOR.filter.allowedContentRules}. It may be used when a static
|
4055
|
+
* {@link #styleableElements} property is not enough to inform the {@link CKEDITOR.filter}
|
4056
|
+
* what HTML features should be enabled when allowing the given style.
|
4057
|
+
*
|
4058
|
+
* In most cases, when style's classes just have to be added to element name(s) used by
|
4059
|
+
* the widget element, it is recommended to use simpler {@link #styleableElements} property.
|
4060
|
+
*
|
4061
|
+
* In order to get parsed classes from the style definition you can use
|
4062
|
+
* {@link CKEDITOR.style.customHandlers.widget#getClassesArray}.
|
4063
|
+
*
|
4064
|
+
* For example, if you want to use the [object format of allowed content rules](#!/guide/dev_allowed_content_rules-section-object-format),
|
4065
|
+
* to specify `match` validator, your implementation could look like this:
|
4066
|
+
*
|
4067
|
+
* editor.widgets.add( 'customWidget', {
|
4068
|
+
* // ...
|
4069
|
+
*
|
4070
|
+
* styleToAllowedContentRules: funciton( style ) {
|
4071
|
+
* // Retrieve classes defined in the style.
|
4072
|
+
* var classes = style.getClassesArray();
|
4073
|
+
*
|
4074
|
+
* // Do something crazy - for example return allowed content rules in object format,
|
4075
|
+
* // with custom match property and propertiesOnly flag.
|
4076
|
+
* return {
|
4077
|
+
* h1: {
|
4078
|
+
* match: isWidgetElement,
|
4079
|
+
* propertiesOnly: true,
|
4080
|
+
* classes: classes
|
4081
|
+
* }
|
4082
|
+
* };
|
4083
|
+
* }
|
4084
|
+
* } );
|
4085
|
+
*
|
4086
|
+
* @since 4.4
|
4087
|
+
* @property {Function} styleToAllowedContentRules
|
4088
|
+
* @param {CKEDITOR.style.customHandlers.widget} style The style to be transformed.
|
4089
|
+
* @returns {CKEDITOR.filter.allowedContentRules}
|
4090
|
+
*/
|
4091
|
+
|
4092
|
+
/**
|
4093
|
+
* This is an abstract class that describes the definition of a widget's nested editable.
|
4094
|
+
* It is a type of values in the {@link CKEDITOR.plugins.widget.definition#editables} object.
|
4095
|
+
*
|
4096
|
+
* In the simplest case the definition is a string which is a CSS selector used to
|
4097
|
+
* find an element that will become a nested editable inside the widget. Note that
|
4098
|
+
* the widget element can be a nested editable, too.
|
4099
|
+
*
|
4100
|
+
* In the more advanced case a definition is an object with a required `selector` property.
|
4101
|
+
*
|
4102
|
+
* editables: {
|
4103
|
+
* header: 'h1',
|
4104
|
+
* content: {
|
4105
|
+
* selector: 'div.content',
|
4106
|
+
* allowedContent: 'p strong em; a[!href]'
|
4107
|
+
* }
|
4108
|
+
* }
|
4109
|
+
*
|
4110
|
+
* @class CKEDITOR.plugins.widget.nestedEditable.definition
|
4111
|
+
* @abstract
|
4112
|
+
*/
|
4113
|
+
|
4114
|
+
/**
|
4115
|
+
* The CSS selector used to find an element which will become a nested editable.
|
4116
|
+
*
|
4117
|
+
* @property {String} selector
|
4118
|
+
*/
|
4119
|
+
|
4120
|
+
/**
|
4121
|
+
* The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules
|
4122
|
+
* which will be used to limit the content allowed in this nested editable.
|
4123
|
+
* This option is similar to {@link CKEDITOR.config#allowedContent} and one can
|
4124
|
+
* use it to limit the editor features available in the nested editable.
|
4125
|
+
*
|
4126
|
+
* If no `allowedContent` is specified, the editable will use the editor default
|
4127
|
+
* {@link CKEDITOR.editor#filter}.
|
4128
|
+
*
|
4129
|
+
* @property {CKEDITOR.filter.allowedContentRules} allowedContent
|
4130
|
+
*/
|
4131
|
+
|
4132
|
+
/**
|
4133
|
+
* The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules
|
4134
|
+
* which will be used to blacklist elements within this nested editable.
|
4135
|
+
* This option is similar to {@link CKEDITOR.config#disallowedContent}.
|
4136
|
+
*
|
4137
|
+
* Note that `disallowedContent` work on top of the definition's {@link #allowedContent}.
|
4138
|
+
*
|
4139
|
+
* @since 4.7.3
|
4140
|
+
* @property {CKEDITOR.filter.disallowedContentRules} disallowedContent
|
4141
|
+
*/
|
4142
|
+
|
4143
|
+
/**
|
4144
|
+
* Nested editable name displayed in the elements path.
|
4145
|
+
*
|
4146
|
+
* @property {String} pathName
|
4147
|
+
*/
|