communication-react-19
Version:
React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)
186 lines • 7.81 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* Plugin event type for RoosterJS plugins
* @private
*/
export var PluginEventType;
(function (PluginEventType) {
PluginEventType["EditorReady"] = "editorReady";
PluginEventType["BeforeDispose"] = "beforeDispose";
PluginEventType["ContentChanged"] = "contentChanged";
PluginEventType["BeforeSetContent"] = "beforeSetContent";
PluginEventType["Input"] = "input";
PluginEventType["KeyDown"] = "keyDown";
PluginEventType["BeforeCutCopy"] = "beforeCutCopy";
PluginEventType["BeforePaste"] = "beforePaste";
PluginEventType["ZoomChanged"] = "zoomChanged";
PluginEventType["MouseUp"] = "mouseUp";
PluginEventType["CompositionEnd"] = "compositionEnd";
})(PluginEventType || (PluginEventType = {}));
/**
* ContentChanged event source for RoosterJS
* @private
*/
export var ContentChangedEventSource;
(function (ContentChangedEventSource) {
ContentChangedEventSource["Paste"] = "Paste";
})(ContentChangedEventSource || (ContentChangedEventSource = {}));
/**
* Applies the border format to the specified element.
* If the element is an HTMLTableCellElement, it skips setting editing info
* and to use classes instead of inline styles.
* For all other cases, the default format applier is used.
*/
export const borderApplier = (format, element, context) => {
if (element instanceof HTMLTableCellElement) {
// don't set format for table cell
// as it will set inline styles for them
// we want to use classes instead
}
else if (context.defaultFormatAppliers.border) {
// apply default formats for all other cases
context.defaultFormatAppliers.border(format, element, context);
}
};
/**
* Applies the dataset format to the given HTML element.
* If the element is an HTMLTableElement, it skips setting editing info
* and to use classes instead of inline styles.
* For all other cases, it applies the default formats.
*/
export const dataSetApplier = (format, element, context) => {
if (element instanceof HTMLTableElement) {
// don't set editing info for tables
// as it will set inline styles for them
// we want to use classes instead
}
else if (context.defaultFormatAppliers.dataset) {
// apply default formats for all other cases
context.defaultFormatAppliers.dataset(format, element, context);
}
};
/* @conditional-compile-remove(rich-text-editor-image-upload) */
/**
* @internal
*/
export const getPreviousInlineImages = (content) => {
if (!content) {
return [];
}
const previousInlineImages = [];
const document = new DOMParser().parseFromString(content !== null && content !== void 0 ? content : '', 'text/html');
document.querySelectorAll('img').forEach((img) => {
const imageAttributes = getInlineImageAttributes(img);
previousInlineImages.push(imageAttributes);
});
return previousInlineImages;
};
/* @conditional-compile-remove(rich-text-editor-image-upload) */
/**
* @internal
*/
export const getRemovedInlineImages = (content, previousInlineImages) => {
const document = new DOMParser().parseFromString(content !== null && content !== void 0 ? content : '', 'text/html');
const currentContentIds = Array.from(document.querySelectorAll('img')).map((img) => img.id);
previousInlineImages = previousInlineImages === null || previousInlineImages === void 0 ? void 0 : previousInlineImages.filter((img) => !!img.id && !(currentContentIds === null || currentContentIds === void 0 ? void 0 : currentContentIds.includes(img.id)));
const removedInlineImages = [...previousInlineImages];
return removedInlineImages;
};
/* @conditional-compile-remove(rich-text-editor-image-upload) */
/**
* @internal
*/
export const getInsertedInlineImages = (content, previousInlineImages) => {
const document = new DOMParser().parseFromString(content !== null && content !== void 0 ? content : '', 'text/html');
const currentContentInlineImages = Array.from(document.querySelectorAll('img'));
const previousContentIds = Array.from(previousInlineImages).map((img) => img.id);
// if check is updated, also update getRemovedInlineImages
const insertedInlineImages = currentContentInlineImages.filter((img) => !previousContentIds.includes(img.id));
return insertedInlineImages;
};
/* @conditional-compile-remove(rich-text-editor-image-upload) */
/**
* @internal
*/
export const getInlineImageAttributes = (image) => {
const imageAttributes = {};
image.getAttributeNames().forEach((attrName) => {
const attrValue = image.getAttribute(attrName);
if (attrValue) {
imageAttributes[attrName] = attrValue;
}
});
return imageAttributes;
};
/* @conditional-compile-remove(rich-text-editor) */
/**
* @internal
*/
/**
* Update the scroll position of the editor to ensure the content is visible.
*/
export const scrollToBottomRichTextEditor = () => {
// Get the current selection in the document
const selection = document.getSelection();
// Check if a selection exists and it has at least one range
if (!selection || selection.rangeCount <= 0) {
// If no selection or range, exit the function
return;
}
// Get the first range of the selection
// A user can normally only select one range at a time, so the rangeCount will usually be 1
const range = selection.getRangeAt(0);
// If the common ancestor container of the range is the document itself,
// it might mean that the editable element is getting removed from the DOM
// In such cases, especially in Safari, trying to modify the range might throw a HierarchyRequest error
if (range.commonAncestorContainer === document) {
return;
}
// Create a temporary span element to use as an anchor for scrolling
// We can't use the anchor node directly because if it's a Text node, calling scrollIntoView() on it will throw an error
const tempElement = document.createElement('span');
// Collapse the range to its end point
// This means the start and end points of the range will be the same, and it will not contain any content
range.collapse(false);
// Insert the temporary element at the cursor's position at the end of the range
range.insertNode(tempElement);
// Scroll the temporary element into view
// the element will be aligned at the center of the scroll container, otherwise, text and images may be positioned incorrectly
tempElement.scrollIntoView({
block: 'center'
});
tempElement.remove();
};
/* @conditional-compile-remove(rich-text-editor-image-upload) */
/**
* Revoke the blob urls in the removedInlineImages and remove them from the currentLocalBlobMap
* @internal
*/
export const removeLocalBlobs = (currentLocalBlobMap, removedInlineImages) => {
removedInlineImages.forEach((image) => {
if (!image.id) {
return;
}
removeSingleLocalBlob(currentLocalBlobMap, image.id);
});
};
/* @conditional-compile-remove(rich-text-editor-image-upload) */
/**
* Revoke all the blob urls in the currentLocalBlobMap and clean up the currentLocalBlobMap
* @internal
*/
export const cleanAllLocalBlobs = (currentLocalBlobMap) => {
Object.keys(currentLocalBlobMap).forEach((imageId) => {
removeSingleLocalBlob(currentLocalBlobMap, imageId);
});
};
/* @conditional-compile-remove(rich-text-editor-image-upload) */
const removeSingleLocalBlob = (currentLocalBlobMap, imageId) => {
const blobUrl = currentLocalBlobMap[imageId];
if (blobUrl) {
URL.revokeObjectURL(blobUrl);
delete currentLocalBlobMap[imageId];
}
};
//# sourceMappingURL=RichTextEditorUtils.js.map