yuku / textcomplete

Autocomplete for HTMLTextAreaElement and more.
https://yuku.takahashi.coffee/textcomplete/
MIT License
1.74k stars 303 forks source link

Insert HTML #337

Open BaptisteVasseur opened 3 years ago

BaptisteVasseur commented 3 years ago

Hi !

I switched from jquery-textcomplete to the new version but I can no longer paste html content in my contenteditable div, it is automatically escaped.

How can I solve my problem ?

I guess the problem is in the ContenteditableEditor file (applySearchResult method) with insertText

this.el.ownerDocument.execCommand(
    "insertText",
     false,
     replace[0] + replace[1]
)
ShaMan123 commented 3 years ago

+1

ShaMan123 commented 3 years ago

217

ShaMan123 commented 3 years ago

I found a solution. This patches SearchResult#replace ContenteditableEditor#applySearchResult in order for the methods to handle html.


import { ContenteditableEditor } from "@textcomplete/contenteditable";
import {
  SearchResult,
  StrategyProps,
  Textcomplete,
  TextcompleteOption
} from "@textcomplete/core";

SearchResult.prototype.replace = function (
  this: SearchResult,
  beforeCursor: string,
  afterCursor: string
): [DocumentFragment, DocumentFragment] | void {
  let result = this.strategy.replace(this.data);
  if (result == null) return;
  if (Array.isArray(result)) {
    afterCursor = result[1] + afterCursor;
    result = result[0];
  }
  const match = this.strategy.match(beforeCursor);
  if (match == null || match.index == null) return;
  const fragment = document.createDocumentFragment();
  fragment.append(
    beforeCursor.slice(0, match.index),
    result,
    beforeCursor.slice(match.index + match[0].length)
  );
  const afterFragment = document.createDocumentFragment();
  afterFragment.append(afterCursor);
  return [fragment, afterFragment];
};

ContenteditableEditor.prototype.applySearchResult = function (
  this: ContenteditableEditor,
  searchResult
) {
  const before = this.getBeforeCursor();
  const after = this.getAfterCursor();
  if (before != null && after != null) {
    const replace = searchResult.replace(before, after) as [
      DocumentFragment,
      DocumentFragment
    ];
    if (Array.isArray(replace)) {
      const range = this.getRange() as Range;
      range.selectNode(range.startContainer);
      range.deleteContents();
      const last = replace[0].lastChild;
      range.insertNode(replace[1]);
      range.insertNode(replace[0]);
      range.detach();
      const newRange = this.getRange();
      newRange.setStartAfter(last);
      newRange.collapse(true);
    }
  }
};
ShaMan123 commented 3 years ago

I have noticed that for string values sometimes the dropdown doesn't show. This is caused by a wrong value of beforeCursor.

ShaMan123 commented 3 years ago

https://codesandbox.io/s/async-fast-1f8yx?file=/src/App.tsx