@ckeditor/ckeditor5-minimap
Version:
Content minimap feature for CKEditor 5.
98 lines (97 loc) • 3.71 kB
JavaScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/* global CSSMediaRule */
/**
* @module minimap/utils
*/
import { Rect, global } from 'ckeditor5/src/utils.js';
import { DomConverter, Renderer } from 'ckeditor5/src/engine.js';
/**
* Clones the editing view DOM root by using a dedicated pair of {@link module:engine/view/renderer~Renderer} and
* {@link module:engine/view/domconverter~DomConverter}. The DOM root clone updates incrementally to stay in sync with the
* source root.
*
* @internal
* @param editor The editor instance the original editing root belongs to.
* @param rootName The name of the root to clone.
* @returns The editing root DOM clone element.
*/
export function cloneEditingViewDomRoot(editor, rootName) {
const viewDocument = editor.editing.view.document;
const viewRoot = viewDocument.getRoot(rootName);
const domConverter = new DomConverter(viewDocument);
const renderer = new Renderer(domConverter, viewDocument.selection);
const domRootClone = editor.editing.view.getDomRoot().cloneNode();
domConverter.bindElements(domRootClone, viewRoot);
renderer.markToSync('children', viewRoot);
renderer.markToSync('attributes', viewRoot);
viewRoot.on('change:children', (evt, node) => renderer.markToSync('children', node));
viewRoot.on('change:attributes', (evt, node) => renderer.markToSync('attributes', node));
viewRoot.on('change:text', (evt, node) => renderer.markToSync('text', node));
renderer.render();
editor.editing.view.on('render', () => renderer.render());
// TODO: Cleanup after destruction.
editor.on('destroy', () => {
domConverter.unbindDomElement(domRootClone);
});
return domRootClone;
}
/**
* Harvests all web page styles, for instance, to allow re-using them in an `<iframe>` preserving the look of the content.
*
* The returned data format is as follows:
*
* ```ts
* [
* 'p { color: red; ... } h2 { font-size: 2em; ... } ...',
* '.spacing { padding: 1em; ... }; ...',
* '...',
* { href: 'http://link.to.external.stylesheet' },
* { href: '...' }
* ]
* ```
*
* **Note**: For stylesheets with `href` different than window origin, an object is returned because
* accessing rules of these styles may cause CORS errors (depending on the configuration of the web page).
*
* @internal
*/
export function getPageStyles() {
return Array.from(global.document.styleSheets)
.map(styleSheet => {
// CORS
if (styleSheet.href && !styleSheet.href.startsWith(global.window.location.origin)) {
return { href: styleSheet.href };
}
return Array.from(styleSheet.cssRules)
.filter(rule => !(rule instanceof CSSMediaRule))
.map(rule => rule.cssText)
.join(' \n');
});
}
/**
* Gets dimensions rectangle according to passed DOM element. Returns whole window's size for `body` element.
*
* @internal
*/
export function getDomElementRect(domElement) {
return new Rect(domElement === global.document.body ? global.window : domElement);
}
/**
* Gets client height according to passed DOM element. Returns window's height for `body` element.
*
* @internal
*/
export function getClientHeight(domElement) {
return domElement === global.document.body ? global.window.innerHeight : domElement.clientHeight;
}
/**
* Returns the DOM element itself if it's not a `body` element, whole window otherwise.
*
* @internal
*/
export function getScrollable(domElement) {
return domElement === global.document.body ? global.window : domElement;
}