Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 | 1x 1x 1x 125x 151x 125x 124x 124x 977x 1x 94x 94x 703x 703x 703x 1x 94x 94x 1x 7x 1x 31x 29x 27x 2x 2x 1x 195x 1x 122x 121x 1x 51x 50x 1x 1x 185x 184x 1x 162x 1x 252x 1x 104x 102x 1x 2x 1x 1x 750x 1x 62x 59x 1x 2636x 2635x 1x 205x 202x 201x 201x 201x 1x 467x 466x 1x 2x 1x 1x 32x 1x 4x 3x 2x 2x 2x 1x 108x 1x 2x 1x 1x 2x 1x 1x 107x 1x 1x 47x 1x 66x 1x 44x 1x 86x 85x 1x 2x 1x 1x 6x 1x 5866x 28x 5838x 125x 20x 17x 3x 3x 2x 2x 2x 2x 3x 1x | import { createEditorCore } from './core/createEditorCore'; import { createEmptyModel, ChangeSource, cloneModel, transformColor, createDomToModelContextWithConfig, domToContentModel, } from 'roosterjs-content-model-dom'; import type { ContentModelDocument, ContentModelFormatter, ContentModelSegmentFormat, DarkColorHandler, DOMEventRecord, DOMHelper, DOMSelection, EditorEnvironment, FormatContentModelOptions, IEditor, PluginEventData, PluginEventFromType, PluginEventType, Snapshot, SnapshotsManager, EditorCore, EditorOptions, Rect, EntityState, CachedElementHandler, DomToModelOptionForCreateModel, AnnounceData, ExperimentalFeature, LegacyTrustedHTMLHandler, DOMCreator, } from 'roosterjs-content-model-types'; /** * The main editor class based on Content Model */ export class Editor implements IEditor { private core: EditorCore | null = null; /** * Creates an instance of Editor * @param contentDiv The DIV HTML element which will be the container element of editor * @param options An optional options object to customize the editor */ constructor(contentDiv: HTMLDivElement, options: EditorOptions = {}) { this.core = createEditorCore(contentDiv, options); const initialModel = options.initialModel ?? createEmptyModel(options.defaultSegmentFormat); this.core.api.setContentModel( this.core, initialModel, { ignoreSelection: true }, undefined /*onNodeCreated*/, true /*isInitializing*/ ); this.core.plugins.forEach(plugin => plugin.initialize(this)); } /** * Dispose this editor, dispose all plugins and custom data */ dispose() { const core = this.getCore(); for (let i = core.plugins.length - 1; i >= 0; i--) { const plugin = core.plugins[i]; try { plugin.dispose(); } catch (e) { // Cache the error and pass it out, then keep going since dispose should always succeed core.disposeErrorHandler?.(plugin, e as Error); } } core.darkColorHandler.reset(); this.core = null; } /** * Get whether this editor is disposed * @returns True if editor is disposed, otherwise false */ isDisposed(): boolean { return !this.core; } /** * Create Content Model from DOM tree in this editor * @param mode What kind of Content Model we want. Currently we support the following values: * - disconnected: Returns a disconnected clone of Content Model from editor which you can do any change on it and it won't impact the editor content. * If there is any entity in editor, the returned object will contain cloned copy of entity wrapper element. * If editor is in dark mode, the cloned entity will be converted back to light mode. * - clean: Similar with disconnected, this will return a disconnected model, the difference is "clean" mode will not include any selection info. * This is usually used for exporting content */ getContentModelCopy(mode: 'connected' | 'disconnected' | 'clean'): ContentModelDocument { const core = this.getCore(); switch (mode) { case 'connected': // Get a connected model is deprecated. Now we will always return disconnected model case 'disconnected': return cloneModel( core.api.createContentModel(core, { tryGetFromCache: false, }), { includeCachedElement: this.cloneOptionCallback, } ); case 'clean': const domToModelContext = createDomToModelContextWithConfig( core.environment.domToModelSettings.calculated, core.api.createEditorContext(core, false /*saveIndex*/) ); return cloneModel(domToContentModel(core.physicalRoot, domToModelContext), { includeCachedElement: this.cloneOptionCallback, }); } } /** * Get current running environment, such as if editor is running on Mac */ getEnvironment(): EditorEnvironment { return this.getCore().environment; } /** * Get current DOM selection */ getDOMSelection(): DOMSelection | null { const core = this.getCore(); return core.api.getDOMSelection(core); } /** * Set DOMSelection into editor content. * @param selection The selection to set */ setDOMSelection(selection: DOMSelection | null) { const core = this.getCore(); core.api.setDOMSelection(core, selection); } /** * Set a new logical root (most likely due to focus change) * @param logicalRoot The new logical root (has to be child of physicalRoot) */ setLogicalRoot(logicalRoot: HTMLDivElement) { const core = this.getCore(); core.api.setLogicalRoot(core, logicalRoot); } /** * The general API to do format change with Content Model * It will grab a Content Model for current editor content, and invoke a callback function * to do format change. Then according to the return value, write back the modified content model into editor. * If there is cached model, it will be used and updated. * @param formatter Formatter function, see ContentModelFormatter * @param options More options, see FormatContentModelOptions */ formatContentModel( formatter: ContentModelFormatter, options?: FormatContentModelOptions, domToModelOptions?: DomToModelOptionForCreateModel ): void { const core = this.getCore(); core.api.formatContentModel(core, formatter, options, domToModelOptions); } /** * Get pending format of editor if any, or return null */ getPendingFormat(): ContentModelSegmentFormat | null { return this.getCore().format.pendingFormat?.format ?? null; } /** * Get a DOM Helper object to help access DOM tree in editor */ getDOMHelper(): DOMHelper { return this.getCore().domHelper; } /** * Add a single undo snapshot to undo stack * @param entityState @optional State for entity if we want to add entity state for this snapshot */ takeSnapshot(entityState?: EntityState): Snapshot | null { const core = this.getCore(); return core.api.addUndoSnapshot( core, false /*canUndoByBackspace*/, entityState ? [entityState] : undefined ); } /** * Restore an undo snapshot into editor * @param snapshot The snapshot to restore */ restoreSnapshot(snapshot: Snapshot): void { const core = this.getCore(); core.api.restoreUndoSnapshot(core, snapshot); } /** * Get document which contains this editor * @returns The HTML document which contains this editor */ getDocument(): Document { return this.getCore().physicalRoot.ownerDocument; } /** * Focus to this editor, the selection was restored to where it was before, no unexpected scroll. */ focus() { const core = this.getCore(); core.api.focus(core); } /** * Check if focus is in editor now * @returns true if focus is in editor, otherwise false */ hasFocus(): boolean { const core = this.getCore(); return core.domHelper.hasFocus(); } /** * Trigger an event to be dispatched to all plugins * @param eventType Type of the event * @param data data of the event with given type, this is the rest part of PluginEvent with the given type * @param broadcast indicates if the event needs to be dispatched to all plugins * True means to all, false means to allow exclusive handling from one plugin unless no one wants that * @returns the event object which is really passed into plugins. Some plugin may modify the event object so * the result of this function provides a chance to read the modified result */ triggerEvent<T extends PluginEventType>( eventType: T, data: PluginEventData<T>, broadcast: boolean = false ): PluginEventFromType<T> { const core = this.getCore(); const event = ({ eventType, ...data, } as any) as PluginEventFromType<T>; core.api.triggerEvent(core, event, broadcast); return event; } /** * Attach a DOM event to the editor content DIV * @param eventMap A map from event name to its handler */ attachDomEvent(eventMap: Record<string, DOMEventRecord>): () => void { const core = this.getCore(); return core.api.attachDomEvent(core, eventMap); } /** * Get undo snapshots manager */ getSnapshotsManager(): SnapshotsManager { const core = this.getCore(); return core.undo.snapshotsManager; } /** * Check if the editor is in dark mode * @returns True if the editor is in dark mode, otherwise false */ isDarkMode(): boolean { return this.getCore().lifecycle.isDarkMode; } /** * Set the dark mode state and transforms the content to match the new state. * @param isDarkMode The next status of dark mode. True if the editor should be in dark mode, false if not. */ setDarkModeState(isDarkMode?: boolean) { const core = this.getCore(); if (!!isDarkMode != core.lifecycle.isDarkMode) { transformColor( core.physicalRoot, false /*includeSelf*/, isDarkMode ? 'lightToDark' : 'darkToLight', core.darkColorHandler ); core.lifecycle.isDarkMode = !!isDarkMode; core.api.triggerEvent( core, { eventType: 'contentChanged', source: isDarkMode ? ChangeSource.SwitchToDarkMode : ChangeSource.SwitchToLightMode, }, true ); } } /** * Check if editor is in Shadow Edit mode */ isInShadowEdit() { return !!this.getCore().lifecycle.shadowEditFragment; } /** * Make the editor in "Shadow Edit" mode. * In Shadow Edit mode, all format change will finally be ignored. * This can be used for building a live preview feature for format button, to allow user * see format result without really apply it. * This function can be called repeated. If editor is already in shadow edit mode, we can still * use this function to do more shadow edit operation. */ startShadowEdit() { const core = this.getCore(); core.api.switchShadowEdit(core, true /*isOn*/); } /** * Leave "Shadow Edit" mode, all changes made during shadow edit will be discarded */ stopShadowEdit() { const core = this.getCore(); core.api.switchShadowEdit(core, false /*isOn*/); } /** * Get a color manager object for this editor. */ getColorManager(): DarkColorHandler { return this.getCore().darkColorHandler; } /** * @deprecated * Get a function to convert HTML string to trusted HTML string. * By default it will just return the input HTML directly. To override this behavior, * pass your own trusted HTML handler to EditorOptions.trustedHTMLHandler * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types */ getTrustedHTMLHandler(): LegacyTrustedHTMLHandler { return this.getCore().trustedHTMLHandler; } /** * Get a function to convert HTML string to a trust Document. * By default it will just convert the original HTML string into a Document object directly. * To override, pass your own trusted HTML handler to EditorOptions.trustedHTMLHandler * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types */ getDOMCreator(): DOMCreator { return this.getCore().domCreator; } /** * Get the scroll container of the editor */ getScrollContainer(): HTMLElement { return this.getCore().domEvent.scrollContainer; } /** * Retrieves the rect of the visible viewport of the editor. */ getVisibleViewport(): Rect | null { return this.getCore().api.getVisibleViewport(this.getCore()); } /** * Add CSS rules for editor * @param key A string to identify the CSS rule type. When set CSS rules with the same key again, existing rules with the same key will be replaced. * @param cssRule The CSS rule string, must be a valid CSS rule string, or browser may throw exception. Pass null to clear existing rules * @param subSelectors @optional If the rule is used for child element under editor, use this parameter to specify the child elements. Each item will be * combined with root selector together to build a separate rule. */ setEditorStyle( key: string, cssRule: string | null, subSelectors?: 'before' | 'after' | string[] ): void { const core = this.getCore(); core.api.setEditorStyle(core, key, cssRule, subSelectors); } /** * Announce the given data * @param announceData Data to announce */ announce(announceData: AnnounceData): void { const core = this.getCore(); core.api.announce(core, announceData); } /** * Check if a given feature is enabled * @param featureName The name of feature to check */ isExperimentalFeatureEnabled(featureName: ExperimentalFeature | string): boolean { return this.getCore().experimentalFeatures.indexOf(featureName) >= 0; } /** * @returns the current EditorCore object * @throws a standard Error if there's no core object */ protected getCore(): EditorCore { if (!this.core) { throw new Error('Editor is already disposed'); } return this.core; } private cloneOptionCallback: CachedElementHandler = (node, type) => { if (type == 'cache') { return undefined; } const result = node.cloneNode(true /*deep*/) as HTMLElement; if (this.isDarkMode()) { const colorHandler = this.getColorManager(); transformColor(result, true /*includeSelf*/, 'darkToLight', colorHandler); result.style.color = result.style.color || 'inherit'; result.style.backgroundColor = result.style.backgroundColor || 'inherit'; } return result; }; } |