如何解决CkEditor 5 自定义拼写检查
我正在使用 Angular 11 并与 CkEditor 5 组件 集成。我需要拼写检查功能,但注意到这不是免费的!所以我决定自己做。
参考下面的存储库,我写下了 ckeditor
组件的角度指令:
https://github.com/uladzimir-miadzinski/ngx-spellchecker/tree/master/src
我不得不进行一些调整以使其与 CkEditor 5 兼容(拼写检查存储库适用于 CkEditor 4!)。 所以在我的角度项目中,我有:
<ckeditor [editor]="editor"
#htmlEditor
[config]="ckEditorConfig"
data="{{input}}"
(ready)="CkEditorMy.onEditorReady($event)"
(change)="onEditorChange($event)"
appSpellCheck
[htmlEditor]="htmlEditor">
</ckeditor>
其中 appSpellCheck
是我的指令:
import { AfterViewInit,Directive,ElementRef,HostListener,Input,OnInit } from '@angular/core';
import { Misspelled,SpellCheckService } from '../../services/spell-check.service';
import { CkEditorMy } from '../lib/ckeditor';
import { debounce } from '../lib/delay.decorator';
enum KeyboardButtons {
Delete = 46,Backspace = 8
}
interface StyleOptions {
[key: string]: string;
}
interface SpellcheckerOptions {
style: StyleOptions;
}
@Directive({
selector: '[appSpellCheck]',})
export class SpellCheckDirective implements OnInit,AfterViewInit {
@input() htmlEditor: any;
@input() timeout = 1000;
@input() options: SpellcheckerOptions = {
style: {
'text-decoration': 'none','background': 'transparent',// '#f8d2d4','border-bottom': '1px solid #e00','Box-shadow': 'inset 0 -1px 0 #e00','color': 'inherit','transition': 'background 0.1s cubic-bezier(.33,.66,1)'
}
};
get errorStyle() {
return `
mark.tf-spellchecker-err {
${Object.entries(this.options.style).reduce((acc,[key,value]) => acc + `${key}:${value};`,'')}
}
`;
}
private _contentNode: Element;
constructor(
private el: ElementRef,private spellcheckService: SpellCheckService
) {
}
ngOnInit(): void {
const el = this.el.nativeElement as HTMLInputElement;
const styleInject = document.createElement('style');
styleInject.innerHTML = this.errorStyle;
el.after(styleInject);
el.setAttribute('spellcheck','false');
}
ngAfterViewInit(): void {
// This code cannot take content node since there is not one yet! It is created after...
// this._contentNode = this.getCkEditorContentNode();
}
private getCkEditorContentNode() {
const el = this.el.nativeElement as HTMLInputElement;
const resetNode = this.getChildNodeByClass(el,"ck ck-reset");
if (resetNode) {
const mainNode = this.getChildNodeByClass(resetNode,"ck ck-editor__main");
if (mainNode) {
const contentNode = this.getChildNodeByClass(mainNode,"ck ck-content");
if (contentNode) {
return contentNode;
}
}
}
return null;
}
private getChildNodeByClass(el: Element,className: string) {
for (var i = 0; i < el.children.length; i++) {
const childNode = el.children[i];
if (childNode.className?.startsWith(className)) {
return childNode;
}
}
return null;
}
@HostListener('keypress')
@debounce()
async onKeyPress() {
await this.spellcheck();
}
@HostListener('keyup',['$event'])
async onKeyUp(e) {
if (e.which === KeyboardButtons.Backspace || e.which === KeyboardButtons.Delete) {
await this.spellcheck();
}
}
@HostListener('focus')
@debounce()
async onFocus() {
await this.spellcheck();
}
getMatches(word,text) {
const regex = new RegExp(`\\b${word}\\b`,'gi');
const result = [];
let match = null;
do {
match = regex.exec(text);
if (match) {
result.push(match.index);
}
} while (match);
return result;
}
private async spellcheck() {
if (this.htmlEditor?.editorInstance) {
// const el = this.el.nativeElement as HTMLInputElement;
// const textToSend = el.innerText || '';
let textToSend = CkEditorMy.getPlainText(this.htmlEditor.editorInstance);
if (!this._contentNode) {
this._contentNode = this.getCkEditorContentNode();
}
if (this._contentNode) {
const misspelledWords = await this.spellcheckService.checkText(textToSend);
await this.makeChildNodesWithSpelling(this._contentNode.childNodes,misspelledWords);
}
}
}
private async makeChildNodesWithSpelling(childNodes,misspelledWords: Misspelled[]) {
for (let m = 0; m < misspelledWords.length; m++) {
for (let i = 0; i < childNodes.length; i++) {
await this.makeNodeWithSpelling(childNodes[i],misspelledWords[m]);
}
}
}
private async makeNodeWithSpelling(node,misspelled: Misspelled) {
if (node.hasChildNodes()) {
if (node.nodeName === 'MARK' && node.textContent === node.dataset.misspelled) {
// value inside <mark> not changed
return;
} else if (node.nodeName === 'MARK') {
// value inside <mark> was changed,need to update <mark> node with new spell,or correct
const corrections: Misspelled[] = await this.spellcheckService.checkText(node.textContent);
if (corrections.length) {
// word with another errors
node.dataset.missplled = corrections[0].word;
node.dataset.suggest = corrections[0].suggestions;
} else {
// word without errors
const caret = this.getCaretCharacterOffsetWithin(this._contentNode as HTMLInputElement);
const textNodeFrag = document.createTextNode(node.textContent);
node.parentNode.replaceChild(textNodeFrag,node);
this.setCaretPosition(this._contentNode as HTMLInputElement,caret);
}
}
await this.makeChildNodesWithSpelling(node.childNodes,[misspelled]);
} else if (node.nodeValue) {
// text value of node,the smallest part
let newNodeValue = node.nodeValue;
const matches = this.getMatches(misspelled.word,node.nodeValue);
const mark = document.createElement('mark');
// mark.dataset.suggest = misspelled.suggestions;
mark.dataset.suggest = JSON.stringify(misspelled.suggestions);
mark.dataset.misspelled = misspelled.word;
mark.setAttribute('class','tf-spellchecker-err');
for (let i = matches.length - 1; i >= 0; i--) {
const matchIndex = matches[i];
mark.innerHTML = newNodeValue.substr(matchIndex,misspelled.word.length);
newNodeValue = newNodeValue.slice(0,matchIndex) + mark.outerHTML + newNodeValue.slice(matchIndex + misspelled.word.length);
}
const frag = document.createrange().createContextualFragment(newNodeValue);
const caret = this.getCaretCharacterOffsetWithin(this._contentNode as HTMLInputElement);
node.parentNode.replaceChild(frag,node);
this.setCaretPosition(this._contentNode as HTMLInputElement,caret);
}
}
private getCaretCharacterOffsetWithin(element) {
var caretoffset = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultview || doc.parentwindow;
var sel;
if (typeof win.getSelection !== 'undefined') {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer,range.endOffset);
caretoffset = preCaretRange.toString().length;
}
} else if ((sel = doc.selection) && sel.type != 'Control') {
var textRange = sel.createrange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.movetoElementText(element);
preCaretTextRange.setEndPoint('EndToEnd',textRange);
caretoffset = preCaretTextRange.text.length;
}
return caretoffset;
}
private getcaretposition() {
if (window.getSelection && window.getSelection().getRangeAt) {
var range = window.getSelection().getRangeAt(0);
var selectedobj = window.getSelection();
var rangeCount = 0;
var childNodes = selectedobj.anchorNode.parentNode.childNodes;
for (let i = 0; i < childNodes.length; i++) {
if (childNodes[i] === selectedobj.anchorNode) {
break;
}
if ((childNodes[i] as HTMLInputElement).outerHTML) {
rangeCount += (childNodes[i] as HTMLInputElement).outerHTML.length;
} else if (childNodes[i].nodeType === 3) {
rangeCount += childNodes[i].textContent.length;
}
}
return range.startOffset + rangeCount;
}
return -1;
}
private setCaretPosition(el,sPos) {
var charIndex = 0,range = document.createrange();
range.setStart(el,0);
range.collapse(true);
var nodeStack = [el],node,foundStart = false,stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && sPos >= charIndex && sPos <= nextCharIndex) {
range.setStart(node,sPos - charIndex);
foundStart = true;
}
if (foundStart && sPos >= charIndex && sPos <= nextCharIndex) {
range.setEnd(node,sPos - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
let selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
}
问题:指令做了什么承诺,事实上,如果我把 debugger
放在 spellcheck()
中,我可以看到拼写错误的单词:
恢复执行有东西删除了我的工作!
我在 CkEditor 5 文档中找不到任何关于此的内容,由于我的企业政策,我需要免费使用此功能!谢谢你的帮助! 如果您需要我代码的其他部分来复制问题:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
export interface Misspelled {
suggestions: string[];
word: string;
}
@Injectable({
providedIn: 'root'
})
export class SpellCheckService {
constructor(private http: HttpClient) {
}
async checkText(text: string): Promise<Misspelled[]> {
return new Promise<Misspelled[]>((resolve,reject) => {
resolve([{
word: "Codice",suggestions: [
'CodiceBU','CodiceBUBU','CodiceBUBUBU',]
}]);
});
}
}
export function debounce(delay: number = 300): MethodDecorator {
return (target: Object,propertyKey: string | symbol,descriptor: PropertyDescriptor) => {
const original = descriptor.value;
descriptor.value = function (...args) {
clearTimeout(this.__timeout__);
this.__timeout__ = setTimeout(() => original.apply(this,args),delay);
};
return descriptor;
};
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。