@gechiui/block-editor
Version:
296 lines (245 loc) • 8.92 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _element = require("@gechiui/element");
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _classnames = _interopRequireDefault(require("classnames"));
var _i18n = require("@gechiui/i18n");
var _compose = require("@gechiui/compose");
var _components = require("@gechiui/components");
var _blockSelectionClearer = require("../block-selection-clearer");
var _writingFlow = require("../writing-flow");
/**
* External dependencies
*/
/**
* GeChiUI dependencies
*/
/**
* Internal dependencies
*/
const BODY_CLASS_NAME = 'editor-styles-wrapper';
const BLOCK_PREFIX = 'gc-block';
/**
* Clones stylesheets targetting the editor canvas to the given document. A
* stylesheet is considered targetting the editor a canvas if it contains the
* `editor-styles-wrapper`, `gc-block`, or `gc-block-*` class selectors.
*
* Ideally, this hook should be removed in the future and styles should be added
* explicitly as editor styles.
*
* @param {Document} doc The document to append cloned stylesheets to.
*/
function styleSheetsCompat(doc) {
// Search the document for stylesheets targetting the editor canvas.
Array.from(document.styleSheets).forEach(styleSheet => {
try {
// May fail for external styles.
// eslint-disable-next-line no-unused-expressions
styleSheet.cssRules;
} catch (e) {
return;
}
const {
ownerNode,
cssRules
} = styleSheet;
if (!cssRules) {
return;
} // Generally, ignore inline styles. We add inline styles belonging to a
// stylesheet later, which may or may not match the selectors.
if (ownerNode.tagName !== 'LINK') {
return;
} // Don't try to add the reset styles, which were removed as a dependency
// from `edit-blocks` for the iframe since we don't need to reset admin
// styles.
if (ownerNode.id === 'gc-reset-editor-styles-css') {
return;
}
const isMatch = Array.from(cssRules).find(_ref => {
let {
selectorText
} = _ref;
return selectorText && (selectorText.includes(`.${BODY_CLASS_NAME}`) || selectorText.includes(`.${BLOCK_PREFIX}`));
});
if (isMatch && !doc.getElementById(ownerNode.id)) {
// Display warning once we have a way to add style dependencies to the editor.
// See: https://github.com/GeChiUI/gutenberg/pull/37466.
doc.head.appendChild(ownerNode.cloneNode(true)); // Add inline styles belonging to the stylesheet.
const inlineCssId = ownerNode.id.replace('-css', '-inline-css');
const inlineCssElement = document.getElementById(inlineCssId);
if (inlineCssElement) {
doc.head.appendChild(inlineCssElement.cloneNode(true));
}
}
});
}
/**
* Bubbles some event types (keydown, keypress, and dragover) to parent document
* document to ensure that the keyboard shortcuts and drag and drop work.
*
* Ideally, we should remove event bubbling in the future. Keyboard shortcuts
* should be context dependent, e.g. actions on blocks like Cmd+A should not
* work globally outside the block editor.
*
* @param {Document} doc Document to attach listeners to.
*/
function bubbleEvents(doc) {
const {
defaultView
} = doc;
const {
frameElement
} = defaultView;
function bubbleEvent(event) {
const prototype = Object.getPrototypeOf(event);
const constructorName = prototype.constructor.name;
const Constructor = window[constructorName];
const init = {};
for (const key in event) {
init[key] = event[key];
}
if (event instanceof defaultView.MouseEvent) {
const rect = frameElement.getBoundingClientRect();
init.clientX += rect.left;
init.clientY += rect.top;
}
const newEvent = new Constructor(event.type, init);
const cancelled = !frameElement.dispatchEvent(newEvent);
if (cancelled) {
event.preventDefault();
}
}
const eventTypes = ['dragover'];
for (const name of eventTypes) {
doc.addEventListener(name, bubbleEvent);
}
}
function useParsedAssets(html) {
return (0, _element.useMemo)(() => {
const doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = html;
return Array.from(doc.body.children);
}, [html]);
}
async function loadScript(head, _ref2) {
let {
id,
src
} = _ref2;
return new Promise((resolve, reject) => {
const script = head.ownerDocument.createElement('script');
script.id = id;
if (src) {
script.src = src;
script.onload = () => resolve();
script.onerror = () => reject();
} else {
resolve();
}
head.appendChild(script);
});
}
function Iframe(_ref3, ref) {
var _window$__editorAsset, _window$__editorAsset2;
let {
contentRef,
children,
head,
tabIndex = 0,
...props
} = _ref3;
const [, forceRender] = (0, _element.useReducer)(() => ({}));
const [iframeDocument, setIframeDocument] = (0, _element.useState)();
const [bodyClasses, setBodyClasses] = (0, _element.useState)([]);
const styles = useParsedAssets((_window$__editorAsset = window.__editorAssets) === null || _window$__editorAsset === void 0 ? void 0 : _window$__editorAsset.styles);
const scripts = useParsedAssets((_window$__editorAsset2 = window.__editorAssets) === null || _window$__editorAsset2 === void 0 ? void 0 : _window$__editorAsset2.scripts);
const clearerRef = (0, _blockSelectionClearer.useBlockSelectionClearer)();
const [before, writingFlowRef, after] = (0, _writingFlow.useWritingFlow)();
const setRef = (0, _compose.useRefEffect)(node => {
function setDocumentIfReady() {
const {
contentDocument,
ownerDocument
} = node;
const {
readyState,
documentElement
} = contentDocument;
if (readyState !== 'interactive' && readyState !== 'complete') {
return false;
}
bubbleEvents(contentDocument);
setIframeDocument(contentDocument);
clearerRef(documentElement); // Ideally ALL classes that are added through get_body_class should
// be added in the editor too, which we'll somehow have to get from
// the server in the future (which will run the PHP filters).
setBodyClasses(Array.from(ownerDocument.body.classList).filter(name => name.startsWith('admin-color-') || name === 'gc-embed-responsive'));
contentDocument.dir = ownerDocument.dir;
documentElement.removeChild(contentDocument.head);
documentElement.removeChild(contentDocument.body);
return true;
}
if (setDocumentIfReady()) {
return;
} // Document is not immediately loaded in Firefox.
node.addEventListener('load', () => {
setDocumentIfReady();
});
}, []);
const headRef = (0, _compose.useRefEffect)(element => {
scripts.reduce((promise, script) => promise.then(() => loadScript(element, script)), Promise.resolve()).finally(() => {
// When script are loaded, re-render blocks to allow them
// to initialise.
forceRender();
});
}, []);
const bodyRef = (0, _compose.useMergeRefs)([contentRef, clearerRef, writingFlowRef]);
(0, _element.useEffect)(() => {
if (iframeDocument) {
styleSheetsCompat(iframeDocument);
}
}, [iframeDocument]);
head = (0, _element.createElement)(_element.Fragment, null, (0, _element.createElement)("style", null, 'body{margin:0}'), styles.map(_ref4 => {
let {
tagName,
href,
id,
rel,
media,
textContent
} = _ref4;
const TagName = tagName.toLowerCase();
if (TagName === 'style') {
return (0, _element.createElement)(TagName, {
id,
key: id
}, textContent);
}
return (0, _element.createElement)(TagName, {
href,
id,
rel,
media,
key: id
});
}), head);
return (0, _element.createElement)(_element.Fragment, null, tabIndex >= 0 && before, (0, _element.createElement)("iframe", (0, _extends2.default)({}, props, {
ref: (0, _compose.useMergeRefs)([ref, setRef]),
tabIndex: tabIndex,
title: (0, _i18n.__)('编辑器画布')
}), iframeDocument && (0, _element.createPortal)((0, _element.createElement)(_element.Fragment, null, (0, _element.createElement)("head", {
ref: headRef
}, head), (0, _element.createElement)("body", {
ref: bodyRef,
className: (0, _classnames.default)(BODY_CLASS_NAME, ...bodyClasses)
}, (0, _element.createElement)(_components.__experimentalStyleProvider, {
document: iframeDocument
}, children))), iframeDocument.documentElement)), tabIndex >= 0 && after);
}
var _default = (0, _element.forwardRef)(Iframe);
exports.default = _default;
//# sourceMappingURL=index.js.map