123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698 |
- /**
- * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
- CKEDITOR.plugins.add("floatpanel", {
- requires: "panel"
- });
- (function() {
- var panels = {};
- function getPanel(editor, doc, parentElement, definition, level) {
- // Generates the panel key: docId-eleId-skinName-langDir[-uiColor][-CSSs][-level]
- var key = CKEDITOR.tools.genKey(
- doc.getUniqueId(),
- parentElement.getUniqueId(),
- editor.lang.dir,
- editor.uiColor || "",
- definition.css || "",
- level || ""
- ),
- panel = panels[key];
- if (!panel) {
- panel = panels[key] = new CKEDITOR.ui.panel(doc, definition);
- panel.element = parentElement.append(
- CKEDITOR.dom.element.createFromHtml(panel.render(editor), doc)
- );
- panel.element.setStyles({
- display: "none",
- position: "absolute"
- });
- }
- return panel;
- }
- /**
- * Represents a floating panel UI element.
- *
- * It is reused by rich combos, color combos, menus, etc.
- * and it renders its content using {@link CKEDITOR.ui.panel}.
- *
- * @class
- * @todo
- */
- CKEDITOR.ui.floatPanel = CKEDITOR.tools.createClass({
- /**
- * Creates a floatPanel class instance.
- *
- * @constructor
- * @param {CKEDITOR.editor} editor
- * @param {CKEDITOR.dom.element} parentElement
- * @param {Object} definition Definition of the panel that will be floating.
- * @param {Number} level
- */
- $: function(editor, parentElement, definition, level) {
- definition.forceIFrame = 1;
- // In case of editor with floating toolbar append panels that should float
- // to the main UI element.
- if (
- definition.toolbarRelated &&
- editor.elementMode == CKEDITOR.ELEMENT_MODE_INLINE
- )
- parentElement = CKEDITOR.document.getById("cke_" + editor.name);
- var doc = parentElement.getDocument(),
- panel = getPanel(editor, doc, parentElement, definition, level || 0),
- element = panel.element,
- iframe = element.getFirst(),
- that = this;
- // Disable native browser menu. (https://dev.ckeditor.com/ticket/4825)
- element.disableContextMenu();
- this.element = element;
- this._ = {
- editor: editor,
- // The panel that will be floating.
- panel: panel,
- parentElement: parentElement,
- definition: definition,
- document: doc,
- iframe: iframe,
- children: [],
- dir: editor.lang.dir,
- showBlockParams: null,
- markFirst:
- definition.markFirst !== undefined ? definition.markFirst : true
- };
- editor.on("mode", hide);
- editor.on("resize", hide);
- // When resize of the window is triggered floatpanel should be repositioned according to new dimensions.
- // https://dev.ckeditor.com/ticket/11724. Fixes issue with undesired panel hiding on Android and iOS.
- doc.getWindow().on(
- "resize",
- function() {
- this.reposition();
- },
- this
- );
- // We need a wrapper because events implementation doesn't allow to attach
- // one listener more than once for the same event on the same object.
- // Remember that floatPanel#hide is shared between all instances.
- function hide() {
- that.hide();
- }
- },
- proto: {
- /**
- * @todo
- */
- addBlock: function(name, block) {
- return this._.panel.addBlock(name, block);
- },
- /**
- * @todo
- */
- addListBlock: function(name, multiSelect) {
- return this._.panel.addListBlock(name, multiSelect);
- },
- /**
- * @todo
- */
- getBlock: function(name) {
- return this._.panel.getBlock(name);
- },
- /**
- * Shows the panel block.
- *
- * @param {String} name
- * @param {CKEDITOR.dom.element} offsetParent Positioned parent.
- * @param {Number} corner
- *
- * * For LTR (left to right) oriented editor:
- * * `1` = top-left
- * * `2` = top-right
- * * `3` = bottom-right
- * * `4` = bottom-left
- * * For RTL (right to left):
- * * `1` = top-right
- * * `2` = top-left
- * * `3` = bottom-left
- * * `4` = bottom-right
- *
- * @param {Number} [offsetX=0]
- * @param {Number} [offsetY=0]
- * @param {Function} [callback] A callback function executed when block positioning is done.
- * @todo what do exactly these params mean (especially corner)?
- */
- showBlock: function(
- name,
- offsetParent,
- corner,
- offsetX,
- offsetY,
- callback
- ) {
- var panel = this._.panel,
- block = panel.showBlock(name);
- this._.showBlockParams = [].slice.call(arguments);
- this.allowBlur(false);
- // Record from where the focus is when open panel.
- var editable = this._.editor.editable();
- this._.returnFocus = editable.hasFocus
- ? editable
- : new CKEDITOR.dom.element(CKEDITOR.document.$.activeElement);
- this._.hideTimeout = 0;
- var element = this.element,
- iframe = this._.iframe,
- // Edge prefers iframe's window to the iframe, just like the rest of the browsers (https://dev.ckeditor.com/ticket/13143).
- focused =
- CKEDITOR.env.ie && !CKEDITOR.env.edge
- ? iframe
- : new CKEDITOR.dom.window(iframe.$.contentWindow),
- doc = element.getDocument(),
- positionedAncestor = this._.parentElement.getPositionedAncestor(),
- position = offsetParent.getDocumentPosition(doc),
- positionedAncestorPosition = positionedAncestor
- ? positionedAncestor.getDocumentPosition(doc)
- : { x: 0, y: 0 },
- rtl = this._.dir == "rtl",
- left = position.x + (offsetX || 0) - positionedAncestorPosition.x,
- top = position.y + (offsetY || 0) - positionedAncestorPosition.y;
- // Floating panels are off by (-1px, 0px) in RTL mode. (https://dev.ckeditor.com/ticket/3438)
- if (rtl && (corner == 1 || corner == 4))
- left += offsetParent.$.offsetWidth;
- else if (!rtl && (corner == 2 || corner == 3))
- left += offsetParent.$.offsetWidth - 1;
- if (corner == 3 || corner == 4) top += offsetParent.$.offsetHeight - 1;
- // Memorize offsetParent by it's ID.
- this._.panel._.offsetParentId = offsetParent.getId();
- element.setStyles({
- top: top + "px",
- left: 0,
- display: ""
- });
- // Don't use display or visibility style because we need to
- // calculate the rendering layout later and focus the element.
- element.setOpacity(0);
- // To allow the context menu to decrease back their width
- element.getFirst().removeStyle("width");
- // Report to focus manager.
- this._.editor.focusManager.add(focused);
- // Configure the IFrame blur event. Do that only once.
- if (!this._.blurSet) {
- // With addEventListener compatible browsers, we must
- // useCapture when registering the focus/blur events to
- // guarantee they will be firing in all situations. (https://dev.ckeditor.com/ticket/3068, https://dev.ckeditor.com/ticket/3222 )
- CKEDITOR.event.useCapture = true;
- focused.on(
- "blur",
- function(ev) {
- // As we are using capture to register the listener,
- // the blur event may get fired even when focusing
- // inside the window itself, so we must ensure the
- // target is out of it.
- if (
- !this.allowBlur() ||
- ev.data.getPhase() != CKEDITOR.EVENT_PHASE_AT_TARGET
- )
- return;
- if (this.visible && !this._.activeChild) {
- // [iOS] Allow hide to be prevented if touch is bound
- // to any parent of the iframe blur happens before touch (https://dev.ckeditor.com/ticket/10714).
- if (CKEDITOR.env.iOS) {
- if (!this._.hideTimeout)
- this._.hideTimeout = CKEDITOR.tools.setTimeout(
- doHide,
- 0,
- this
- );
- } else {
- doHide.call(this);
- }
- }
- function doHide() {
- // Panel close is caused by user's navigating away the focus, e.g. click outside the panel.
- // DO NOT restore focus in this case.
- delete this._.returnFocus;
- this.hide();
- }
- },
- this
- );
- focused.on(
- "focus",
- function() {
- this._.focused = true;
- this.hideChild();
- this.allowBlur(true);
- },
- this
- );
- // [iOS] if touch is bound to any parent of the iframe blur
- // happens twice before touchstart and before touchend (https://dev.ckeditor.com/ticket/10714).
- if (CKEDITOR.env.iOS) {
- // Prevent false hiding on blur.
- // We don't need to return focus here because touchend will fire anyway.
- // If user scrolls and pointer gets out of the panel area touchend will also fire.
- focused.on(
- "touchstart",
- function() {
- clearTimeout(this._.hideTimeout);
- },
- this
- );
- // Set focus back to handle blur and hide panel when needed.
- focused.on(
- "touchend",
- function() {
- this._.hideTimeout = 0;
- this.focus();
- },
- this
- );
- }
- CKEDITOR.event.useCapture = false;
- this._.blurSet = 1;
- }
- panel.onEscape = CKEDITOR.tools.bind(function(keystroke) {
- if (this.onEscape && this.onEscape(keystroke) === false) return false;
- }, this);
- CKEDITOR.tools.setTimeout(
- function() {
- var panelLoad = CKEDITOR.tools.bind(function() {
- var target = element;
- // Reset panel width as the new content can be narrower
- // than the old one. (https://dev.ckeditor.com/ticket/9355)
- target.removeStyle("width");
- if (block.autoSize) {
- var panelDoc = block.element.getDocument(),
- width = (CKEDITOR.env.webkit || CKEDITOR.env.edge
- ? block.element
- : panelDoc.getBody()
- ).$.scrollWidth;
- // Account for extra height needed due to IE quirks box model bug:
- // http://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug
- // (https://dev.ckeditor.com/ticket/3426)
- if (CKEDITOR.env.ie && CKEDITOR.env.quirks && width > 0)
- width +=
- (target.$.offsetWidth || 0) -
- (target.$.clientWidth || 0) +
- 3;
- // Add some extra pixels to improve the appearance.
- width += 10;
- target.setStyle("width", width + "px");
- var height = block.element.$.scrollHeight;
- // Account for extra height needed due to IE quirks box model bug:
- // http://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug
- // (https://dev.ckeditor.com/ticket/3426)
- if (CKEDITOR.env.ie && CKEDITOR.env.quirks && height > 0)
- height +=
- (target.$.offsetHeight || 0) -
- (target.$.clientHeight || 0) +
- 3;
- target.setStyle("height", height + "px");
- // Fix IE < 8 visibility.
- panel._.currentBlock.element
- .setStyle("display", "none")
- .removeStyle("display");
- } else {
- target.removeStyle("height");
- }
- // Flip panel layout horizontally in RTL with known width.
- if (rtl) left -= element.$.offsetWidth;
- // Pop the style now for measurement.
- element.setStyle("left", left + "px");
- /* panel layout smartly fit the viewport size. */
- var panelElement = panel.element,
- panelWindow = panelElement.getWindow(),
- rect = element.$.getBoundingClientRect(),
- viewportSize = panelWindow.getViewPaneSize();
- // Compensation for browsers that dont support "width" and "height".
- var rectWidth = rect.width || rect.right - rect.left,
- rectHeight = rect.height || rect.bottom - rect.top;
- // Check if default horizontal layout is impossible.
- var spaceAfter = rtl
- ? rect.right
- : viewportSize.width - rect.left,
- spaceBefore = rtl ? viewportSize.width - rect.right : rect.left;
- if (rtl) {
- if (spaceAfter < rectWidth) {
- // Flip to show on right.
- if (spaceBefore > rectWidth) left += rectWidth;
- // Align to window left.
- else if (viewportSize.width > rectWidth)
- left = left - rect.left;
- // Align to window right, never cutting the panel at right.
- else left = left - rect.right + viewportSize.width;
- }
- } else if (spaceAfter < rectWidth) {
- // Flip to show on left.
- if (spaceBefore > rectWidth) left -= rectWidth;
- // Align to window right.
- else if (viewportSize.width > rectWidth)
- left = left - rect.right + viewportSize.width;
- // Align to window left, never cutting the panel at left.
- else left = left - rect.left;
- }
- // Check if the default vertical layout is possible.
- var spaceBelow = viewportSize.height - rect.top,
- spaceAbove = rect.top;
- if (spaceBelow < rectHeight) {
- // Flip to show above.
- if (spaceAbove > rectHeight) top -= rectHeight;
- // Align to window bottom.
- else if (viewportSize.height > rectHeight)
- top = top - rect.bottom + viewportSize.height;
- // Align to top, never cutting the panel at top.
- else top = top - rect.top;
- }
- // If IE is in RTL, we have troubles with absolute
- // position and horizontal scrolls. Here we have a
- // series of hacks to workaround it. (https://dev.ckeditor.com/ticket/6146)
- if (CKEDITOR.env.ie && !CKEDITOR.env.edge) {
- var offsetParent = new CKEDITOR.dom.element(
- element.$.offsetParent
- ),
- scrollParent = offsetParent;
- // Quirks returns <body>, but standards returns <html>.
- if (scrollParent.getName() == "html") {
- scrollParent = scrollParent.getDocument().getBody();
- }
- if (scrollParent.getComputedStyle("direction") == "rtl") {
- // For IE8, there is not much logic on this, but it works.
- if (CKEDITOR.env.ie8Compat) {
- left -=
- element.getDocument().getDocumentElement().$.scrollLeft *
- 2;
- } else {
- left -=
- offsetParent.$.scrollWidth - offsetParent.$.clientWidth;
- }
- }
- }
- // Trigger the onHide event of the previously active panel to prevent
- // incorrect styles from being applied (https://dev.ckeditor.com/ticket/6170)
- var innerElement = element.getFirst(),
- activePanel;
- if ((activePanel = innerElement.getCustomData("activePanel")))
- activePanel.onHide && activePanel.onHide.call(this, 1);
- innerElement.setCustomData("activePanel", this);
- element.setStyles({
- top: top + "px",
- left: left + "px"
- });
- element.setOpacity(1);
- callback && callback();
- }, this);
- panel.isLoaded ? panelLoad() : (panel.onLoad = panelLoad);
- CKEDITOR.tools.setTimeout(
- function() {
- var scrollTop =
- CKEDITOR.env.webkit &&
- CKEDITOR.document.getWindow().getScrollPosition().y;
- // Focus the panel frame first, so blur gets fired.
- this.focus();
- // Focus the block now.
- block.element.focus();
- // https://dev.ckeditor.com/ticket/10623, https://dev.ckeditor.com/ticket/10951 - restore the viewport's scroll position after focusing list element.
- if (CKEDITOR.env.webkit)
- CKEDITOR.document.getBody().$.scrollTop = scrollTop;
- // We need this get fired manually because of unfired focus() function.
- this.allowBlur(true);
- // Ensure that the first item is focused (https://dev.ckeditor.com/ticket/16804).
- if (this._.markFirst) {
- if (CKEDITOR.env.ie) {
- CKEDITOR.tools.setTimeout(function() {
- block.markFirstDisplayed
- ? block.markFirstDisplayed()
- : block._.markFirstDisplayed();
- }, 0);
- } else {
- block.markFirstDisplayed
- ? block.markFirstDisplayed()
- : block._.markFirstDisplayed();
- }
- }
- this._.editor.fire("panelShow", this);
- },
- 0,
- this
- );
- },
- CKEDITOR.env.air ? 200 : 0,
- this
- );
- this.visible = 1;
- if (this.onShow) this.onShow.call(this);
- },
- /**
- * Repositions the panel with the same parameters that were used in the last {@link #showBlock} call.
- *
- * @since 4.5.4
- */
- reposition: function() {
- var blockParams = this._.showBlockParams;
- if (this.visible && this._.showBlockParams) {
- this.hide();
- this.showBlock.apply(this, blockParams);
- }
- },
- /**
- * Restores the last focused element or simply focuses the panel window.
- */
- focus: function() {
- // Webkit requires to blur any previous focused page element, in
- // order to properly fire the "focus" event.
- if (CKEDITOR.env.webkit) {
- var active = CKEDITOR.document.getActive();
- active && !active.equals(this._.iframe) && active.$.blur();
- }
- // Restore last focused element or simply focus panel window.
- var focus =
- this._.lastFocused || this._.iframe.getFrameDocument().getWindow();
- focus.focus();
- },
- /**
- * @todo
- */
- blur: function() {
- var doc = this._.iframe.getFrameDocument(),
- active = doc.getActive();
- active && active.is("a") && (this._.lastFocused = active);
- },
- /**
- * Hides the panel.
- *
- * @todo
- */
- hide: function(returnFocus) {
- if (this.visible && (!this.onHide || this.onHide.call(this) !== true)) {
- this.hideChild();
- // Blur previously focused element. (https://dev.ckeditor.com/ticket/6671)
- CKEDITOR.env.gecko &&
- this._.iframe.getFrameDocument().$.activeElement.blur();
- this.element.setStyle("display", "none");
- this.visible = 0;
- this.element.getFirst().removeCustomData("activePanel");
- // Return focus properly. (https://dev.ckeditor.com/ticket/6247)
- var focusReturn = returnFocus && this._.returnFocus;
- if (focusReturn) {
- // Webkit requires focus moved out panel iframe first.
- if (CKEDITOR.env.webkit && focusReturn.type)
- focusReturn.getWindow().$.focus();
- focusReturn.focus();
- }
- delete this._.lastFocused;
- this._.showBlockParams = null;
- this._.editor.fire("panelHide", this);
- }
- },
- /**
- * @todo
- */
- allowBlur: function(allow) {
- // Prevent editor from hiding the panel. (https://dev.ckeditor.com/ticket/3222)
- var panel = this._.panel;
- if (allow !== undefined) panel.allowBlur = allow;
- return panel.allowBlur;
- },
- /**
- * Shows the specified panel as a child of one block of this one.
- *
- * @param {CKEDITOR.ui.floatPanel} panel
- * @param {String} blockName
- * @param {CKEDITOR.dom.element} offsetParent Positioned parent.
- * @param {Number} corner
- *
- * * For LTR (left to right) oriented editor:
- * * `1` = top-left
- * * `2` = top-right
- * * `3` = bottom-right
- * * `4` = bottom-left
- * * For RTL (right to left):
- * * `1` = top-right
- * * `2` = top-left
- * * `3` = bottom-left
- * * `4` = bottom-right
- *
- * @param {Number} [offsetX=0]
- * @param {Number} [offsetY=0]
- * @todo
- */
- showAsChild: function(
- panel,
- blockName,
- offsetParent,
- corner,
- offsetX,
- offsetY
- ) {
- // Skip reshowing of child which is already visible.
- if (
- this._.activeChild == panel &&
- panel._.panel._.offsetParentId == offsetParent.getId()
- )
- return;
- this.hideChild();
- panel.onHide = CKEDITOR.tools.bind(function() {
- // Use a timeout, so we give time for this menu to get
- // potentially focused.
- CKEDITOR.tools.setTimeout(
- function() {
- if (!this._.focused) this.hide();
- },
- 0,
- this
- );
- }, this);
- this._.activeChild = panel;
- this._.focused = false;
- panel.showBlock(blockName, offsetParent, corner, offsetX, offsetY);
- this.blur();
- /* https://dev.ckeditor.com/ticket/3767 IE: Second level menu may not have borders */
- if (CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat) {
- setTimeout(function() {
- panel.element.getChild(0).$.style.cssText += "";
- }, 100);
- }
- },
- /**
- * @todo
- */
- hideChild: function(restoreFocus) {
- var activeChild = this._.activeChild;
- if (activeChild) {
- delete activeChild.onHide;
- delete this._.activeChild;
- activeChild.hide();
- // At this point focus should be moved back to parent panel.
- restoreFocus && this.focus();
- }
- }
- }
- });
- CKEDITOR.on("instanceDestroyed", function() {
- var isLastInstance = CKEDITOR.tools.isEmpty(CKEDITOR.instances);
- for (var i in panels) {
- var panel = panels[i];
- // Safe to destroy it since there're no more instances.(https://dev.ckeditor.com/ticket/4241)
- if (isLastInstance) panel.destroy();
- // Panel might be used by other instances, just hide them.(https://dev.ckeditor.com/ticket/4552)
- else panel.element.hide();
- }
- // Remove the registration.
- isLastInstance && (panels = {});
- });
- })();
|