Skip to content

Commit

Permalink
Vhuseinova/watermark plugin update (#2507)
Browse files Browse the repository at this point in the history
* Add update function for Watermark plugin

* Code formatting update
  • Loading branch information
vhuseinova-msft authored Mar 15, 2024
1 parent 4c342fc commit c073faf
Showing 1 changed file with 160 additions and 138 deletions.
298 changes: 160 additions & 138 deletions packages/roosterjs-editor-plugins/lib/plugins/Watermark/Watermark.ts
Original file line number Diff line number Diff line change
@@ -1,138 +1,160 @@
import { applyFormat, getEntitySelector, getTagOfNode } from 'roosterjs-editor-dom';
import { ContentPosition, EntityOperation, PluginEventType } from 'roosterjs-editor-types';
import { insertEntity } from 'roosterjs-editor-api';
import type {
DefaultFormat,
EditorPlugin,
Entity,
IEditor,
PluginEvent,
} from 'roosterjs-editor-types';

const ENTITY_TYPE = 'WATERMARK_WRAPPER';

/**
* A watermark plugin to manage watermark string for roosterjs
*/
export default class Watermark implements EditorPlugin {
private editor: IEditor | null = null;
private disposer: (() => void) | null = null;
private format: DefaultFormat;

/**
* Create an instance of Watermark plugin
* @param watermark The watermark string
*/
constructor(private watermark: string, format?: DefaultFormat, private customClass?: string) {
this.format = format || {
fontSize: '14px',
textColors: {
lightModeColor: '#AAAAAA',
darkModeColor: '#6B6B6B',
},
};
}

/**
* Get a friendly name of this plugin
*/
getName() {
return 'Watermark';
}

/**
* Initialize this plugin. This should only be called from Editor
* @param editor Editor instance
*/
initialize(editor: IEditor) {
this.editor = editor;
this.disposer = this.editor.addDomEventHandler({
focus: this.showHideWatermark,
blur: this.showHideWatermark,
});
}

/**
* Dispose this plugin
*/
dispose() {
this.disposer?.();
this.disposer = null;
this.editor = null;
}

/**
* Handle events triggered from editor
* @param event PluginEvent object
*/
onPluginEvent(event: PluginEvent) {
if (
event.eventType == PluginEventType.EditorReady ||
(event.eventType == PluginEventType.ContentChanged &&
(<Entity>event.data)?.type != ENTITY_TYPE)
) {
this.showHideWatermark();
} else if (
event.eventType == PluginEventType.EntityOperation &&
event.entity.type == ENTITY_TYPE &&
this.editor
) {
const {
operation,
entity: { wrapper },
} = event;
if (operation == EntityOperation.ReplaceTemporaryContent) {
this.removeWatermark(wrapper);
} else if (event.operation == EntityOperation.NewEntity) {
applyFormat(
wrapper,
this.format,
this.editor.isDarkMode(),
this.editor.getDarkColorHandler()
);
wrapper.spellcheck = false;
}
}
}

private showHideWatermark = () => {
if (!this.editor) {
return;
}
const hasFocus = this.editor.hasFocus();
const watermarks = this.editor.queryElements(getEntitySelector(ENTITY_TYPE));
const isShowing = watermarks.length > 0;

if (hasFocus && isShowing) {
watermarks.forEach(this.removeWatermark);
this.editor.focus();
} else if (!hasFocus && !isShowing && this.editor.isEmpty()) {
const newEntity = insertEntity(
this.editor,
ENTITY_TYPE,
this.editor.getDocument().createTextNode(this.watermark),
false /*isBlock*/,
false /*isReadonly*/,
ContentPosition.Begin
);
if (this.customClass) {
newEntity.wrapper.classList.add(this.customClass);
}
}
};

private removeWatermark = (wrapper: HTMLElement) => {
const parentNode = wrapper.parentNode;
parentNode?.removeChild(wrapper);

// After remove watermark node, if it leaves an empty DIV, append a BR node into it to make it a regular empty line
if (
parentNode &&
this.editor?.contains(parentNode) &&
getTagOfNode(parentNode) == 'DIV' &&
!parentNode.firstChild
) {
parentNode.appendChild(this.editor.getDocument().createElement('BR'));
}
};
}
import { applyFormat, getEntitySelector, getTagOfNode } from 'roosterjs-editor-dom';
import { ContentPosition, EntityOperation, PluginEventType } from 'roosterjs-editor-types';
import { insertEntity } from 'roosterjs-editor-api';
import type {
DefaultFormat,
EditorPlugin,
Entity,
IEditor,
PluginEvent,
} from 'roosterjs-editor-types';

const ENTITY_TYPE = 'WATERMARK_WRAPPER';

/**
* A watermark plugin to manage watermark string for roosterjs
*/
export default class Watermark implements EditorPlugin {
private editor: IEditor | null = null;
private disposer: (() => void) | null = null;
private format: DefaultFormat;

/**
* Create an instance of Watermark plugin
* @param watermark The watermark string
*/
constructor(private watermark: string, format?: DefaultFormat, private customClass?: string) {
this.format = format || {
fontSize: '14px',
textColors: {
lightModeColor: '#AAAAAA',
darkModeColor: '#6B6B6B',
},
};
}

/**
* Updates the watermark text.
* @param watermark - The new watermark text.
*/
updateWatermark(watermark: string) {
this.watermark = watermark;

if (!this.editor) {
return;
}
const watermarks = this.editor.queryElements(getEntitySelector(ENTITY_TYPE));
const isShowing = watermarks.length > 0;
// re-render watermark only if it's already displayed
if (isShowing) {
// hide watermark
const watermarks = this.editor.queryElements(getEntitySelector(ENTITY_TYPE));
watermarks.forEach(this.removeWatermark);
// show watermark
this.showHideWatermark();
}
}

/**
* Get a friendly name of this plugin
*/
getName() {
return 'Watermark';
}

/**
* Initialize this plugin. This should only be called from Editor
* @param editor Editor instance
*/
initialize(editor: IEditor) {
this.editor = editor;
this.disposer = this.editor.addDomEventHandler({
focus: this.showHideWatermark,
blur: this.showHideWatermark,
});
}

/**
* Dispose this plugin
*/
dispose() {
this.disposer?.();
this.disposer = null;
this.editor = null;
}

/**
* Handle events triggered from editor
* @param event PluginEvent object
*/
onPluginEvent(event: PluginEvent) {
if (
event.eventType == PluginEventType.EditorReady ||
(event.eventType == PluginEventType.ContentChanged &&
(<Entity>event.data)?.type != ENTITY_TYPE)
) {
this.showHideWatermark();
} else if (
event.eventType == PluginEventType.EntityOperation &&
event.entity.type == ENTITY_TYPE &&
this.editor
) {
const {
operation,
entity: { wrapper },
} = event;
if (operation == EntityOperation.ReplaceTemporaryContent) {
this.removeWatermark(wrapper);
} else if (event.operation == EntityOperation.NewEntity) {
applyFormat(
wrapper,
this.format,
this.editor.isDarkMode(),
this.editor.getDarkColorHandler()
);
wrapper.spellcheck = false;
}
}
}

private showHideWatermark = () => {
if (!this.editor) {
return;
}
const hasFocus = this.editor.hasFocus();
const watermarks = this.editor.queryElements(getEntitySelector(ENTITY_TYPE));
const isShowing = watermarks.length > 0;

if (hasFocus && isShowing) {
watermarks.forEach(this.removeWatermark);
this.editor.focus();
} else if (!hasFocus && !isShowing && this.editor.isEmpty()) {
const newEntity = insertEntity(
this.editor,
ENTITY_TYPE,
this.editor.getDocument().createTextNode(this.watermark),
false /*isBlock*/,
false /*isReadonly*/,
ContentPosition.Begin
);
if (this.customClass) {
newEntity.wrapper.classList.add(this.customClass);
}
}
};

private removeWatermark = (wrapper: HTMLElement) => {
const parentNode = wrapper.parentNode;
parentNode?.removeChild(wrapper);

// After remove watermark node, if it leaves an empty DIV, append a BR node into it to make it a regular empty line
if (
parentNode &&
this.editor?.contains(parentNode) &&
getTagOfNode(parentNode) == 'DIV' &&
!parentNode.firstChild
) {
parentNode.appendChild(this.editor.getDocument().createElement('BR'));
}
};
}

0 comments on commit c073faf

Please sign in to comment.