diff --git a/packages/roosterjs-content-model-plugins/lib/watermark/WatermarkPlugin.ts b/packages/roosterjs-content-model-plugins/lib/watermark/WatermarkPlugin.ts index b09eef7ee89..c64f3cfe3a8 100644 --- a/packages/roosterjs-content-model-plugins/lib/watermark/WatermarkPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/watermark/WatermarkPlugin.ts @@ -1,4 +1,4 @@ -import { getObjectKeys } from 'roosterjs-content-model-dom'; +import { ChangeSource, getObjectKeys } from 'roosterjs-content-model-dom'; import { isModelEmptyFast } from './isModelEmptyFast'; import type { WatermarkFormat } from './WatermarkFormat'; import type { EditorPlugin, IEditor, PluginEvent } from 'roosterjs-content-model-types'; @@ -17,6 +17,7 @@ export class WatermarkPlugin implements EditorPlugin { private editor: IEditor | null = null; private format: WatermarkFormat; private isShowing = false; + private darkTextColor: string | null = null; /** * Create an instance of Watermark plugin @@ -68,6 +69,25 @@ export class WatermarkPlugin implements EditorPlugin { ) { // When input text, editor must not be empty, so we can do hide watermark now without checking content model this.showHide(editor, false /*isEmpty*/); + } else if ( + event.eventType == 'contentChanged' && + (event.source == ChangeSource.SwitchToDarkMode || + event.source == ChangeSource.SwitchToLightMode) && + this.isShowing + ) { + // When the placeholder is shown and user switches the mode, we need to update watermark style + if ( + event.source == ChangeSource.SwitchToDarkMode && + !this.darkTextColor && + this.format.textColor + ) { + // Get the dark color only once when dark mode is enabled for the first time + this.darkTextColor = editor + .getColorManager() + .getDarkColor(this.format.textColor, undefined, 'text'); + } + + this.applyWatermarkStyle(editor); } else if ( event.eventType == 'editorReady' || event.eventType == 'contentChanged' || @@ -93,17 +113,24 @@ export class WatermarkPlugin implements EditorPlugin { } protected show(editor: IEditor) { + this.applyWatermarkStyle(editor); + this.isShowing = true; + } + + private applyWatermarkStyle(editor: IEditor) { let rule = `position: absolute; pointer-events: none; content: "${this.watermark}";`; + const format = { + ...this.format, + textColor: editor.isDarkMode() ? this.darkTextColor : this.format.textColor, + }; getObjectKeys(styleMap).forEach(x => { - if (this.format[x]) { - rule += `${styleMap[x]}: ${this.format[x]}!important;`; + if (format[x]) { + rule += `${styleMap[x]}: ${format[x]}!important;`; } }); editor.setEditorStyle(WATERMARK_CONTENT_KEY, rule, 'before'); - - this.isShowing = true; } protected hide(editor: IEditor) { diff --git a/packages/roosterjs-content-model-plugins/test/watermark/WatermarkPluginTest.ts b/packages/roosterjs-content-model-plugins/test/watermark/WatermarkPluginTest.ts index 66fe12c49ab..220c26ca261 100644 --- a/packages/roosterjs-content-model-plugins/test/watermark/WatermarkPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/watermark/WatermarkPluginTest.ts @@ -1,4 +1,5 @@ import * as isModelEmptyFast from '../../lib/watermark/isModelEmptyFast'; +import { ChangeSource } from 'roosterjs-content-model-dom'; import { IEditor } from 'roosterjs-content-model-types'; import { WatermarkPlugin } from '../../lib/watermark/WatermarkPlugin'; @@ -24,6 +25,7 @@ describe('WatermarkPlugin', () => { editor = { formatContentModel: formatContentModelSpy, setEditorStyle: setEditorStyleSpy, + isDarkMode: () => false, } as any; }); @@ -143,3 +145,113 @@ describe('WatermarkPlugin', () => { expect(setEditorStyleSpy).toHaveBeenCalledTimes(2); }); }); + +describe('WatermarkPlugin dark mode', () => { + let editor: IEditor; + let formatContentModelSpy: jasmine.Spy; + let isModelEmptyFastSpy: jasmine.Spy; + let setEditorStyleSpy: jasmine.Spy; + let getDarkColorSpy: jasmine.Spy; + let isDarkModeSpy: jasmine.Spy; + const DEFAULT_DARK_COLOR_SUFFIX_COLOR = 'DarkColorMock-'; + + const mockedModel = 'Model' as any; + + beforeEach(() => { + isModelEmptyFastSpy = spyOn(isModelEmptyFast, 'isModelEmptyFast'); + setEditorStyleSpy = jasmine.createSpy('setEditorStyle'); + getDarkColorSpy = jasmine.createSpy('getDarkColor'); + isDarkModeSpy = jasmine.createSpy('isDarkMode'); + + formatContentModelSpy = jasmine + .createSpy('formatContentModel') + .and.callFake((callback: Function) => { + const result = callback(mockedModel); + + expect(result).toBeFalse(); + }); + + editor = { + formatContentModel: formatContentModelSpy, + setEditorStyle: setEditorStyleSpy, + isDarkMode: isDarkModeSpy, + getColorManager: () => ({ + getDarkColor: getDarkColorSpy.and.callFake((color: string) => { + return `${DEFAULT_DARK_COLOR_SUFFIX_COLOR}${color}`; + }), + }), + } as any; + }); + + it('Has format, empty editor, with text', () => { + isModelEmptyFastSpy.and.returnValue(true); + const textColor = 'red'; + const plugin = new WatermarkPlugin('test', { + fontFamily: 'Arial', + fontSize: '20pt', + textColor: textColor, + }); + const darkModeStyles = `position: absolute; pointer-events: none; content: "test";font-family: Arial!important;font-size: 20pt!important;color: ${DEFAULT_DARK_COLOR_SUFFIX_COLOR}${textColor}!important;`; + const lightModeStyles = `position: absolute; pointer-events: none; content: "test";font-family: Arial!important;font-size: 20pt!important;color: red!important;`; + + plugin.initialize(editor); + + plugin.onPluginEvent({ eventType: 'editorReady' }); + + expect(formatContentModelSpy).toHaveBeenCalledTimes(1); + expect(setEditorStyleSpy).toHaveBeenCalledTimes(1); + expect(setEditorStyleSpy).toHaveBeenCalledWith( + '_WatermarkContent', + lightModeStyles, + 'before' + ); + expect(getDarkColorSpy).not.toHaveBeenCalled(); + isDarkModeSpy.and.returnValue(true); + plugin.onPluginEvent({ + eventType: 'contentChanged', + source: ChangeSource.SwitchToDarkMode, + rawEvent: {}, + } as any); + + expect(formatContentModelSpy).toHaveBeenCalledTimes(1); + expect(getDarkColorSpy).toHaveBeenCalledTimes(1); + expect(setEditorStyleSpy).toHaveBeenCalledTimes(2); + expect(setEditorStyleSpy).toHaveBeenCalledWith( + '_WatermarkContent', + darkModeStyles, + 'before' + ); + + isDarkModeSpy.and.returnValue(false); + plugin.onPluginEvent({ + eventType: 'contentChanged', + source: ChangeSource.SwitchToLightMode, + rawEvent: {}, + } as any); + + expect(formatContentModelSpy).toHaveBeenCalledTimes(1); + expect(getDarkColorSpy).toHaveBeenCalledTimes(1); + expect(setEditorStyleSpy).toHaveBeenCalledTimes(3); + expect(setEditorStyleSpy).toHaveBeenCalledWith( + '_WatermarkContent', + lightModeStyles, + 'before' + ); + + isDarkModeSpy.and.returnValue(true); + plugin.onPluginEvent({ + eventType: 'contentChanged', + source: ChangeSource.SwitchToDarkMode, + rawEvent: {}, + } as any); + + expect(formatContentModelSpy).toHaveBeenCalledTimes(1); + expect(getDarkColorSpy).toHaveBeenCalledTimes(1); + expect(setEditorStyleSpy).toHaveBeenCalledTimes(4); + expect(setEditorStyleSpy).toHaveBeenCalledWith( + '_WatermarkContent', + darkModeStyles, + 'before' + ); + }); +}); diff --git a/versions.json b/versions.json index d1b1d566ec0..c0a39e64585 100644 --- a/versions.json +++ b/versions.json @@ -2,5 +2,7 @@ "react": "9.0.0", "main": "9.6.0", "legacyAdapter": "8.62.0", - "overrides": {} + "overrides": { + "roosterjs-content-model-plugins": "9.6.1" + } }