I need detect this scenario in order to fetch info about the link. Just like Facebook does.
`import linkifyIt from 'linkify-it';
import tlds from 'tlds';
const linkify = linkifyIt();
linkify.tlds(tlds);`
`_onChange = (editorState) => {
editorState.getCurrentContent().getBlockMap().forEach((block) => {
const links = linkify.match(block.get('text'));
if (typeof links !== 'undefined' && links !== null) {
for (let i = 0; i < links.length; i++) {
THIS_IS_THE_LINK(links[i].url); // could be more than one
}
}
});
this.setState({editorState});
};`
@albertomr86, I'm curios if you've faced same problem as I do right now. I'm using similar approach to detect links but using regex instead of linkify module. The problem is when link entity gets created it continues to more characters if user keeps typing. And it makes sense in some cases because the link could be www.google.c, or www.google.co, or www.google.com. But what to do if user types a space or any other non link related character, how to stop the link entity and fallback to plain text?

The logic I use and the problem is in if (!entityKeyStart) { but how would you do it otherwise, recreate entities all over again on each onChange doesn't seem like a good idea:
const RE_URL = new RegExp(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g);
editorState
.getCurrentContent()
.getBlockMap()
.map((block) => {
let contentState = editorState.getCurrentContent();
const blockKey = block.getKey();
const blockText = block.getText();
let match = null;
while (match = RE_URL.exec(blockText)) {
const start = match.index;
const entityKeyStart = block.getEntityAt(start);
if (!entityKeyStart) {
contentState = contentState.createEntity('LINK', 'MUTABLE', { href: match[0] });
const entityKey = contentState.getLastCreatedEntityKey();
const contentStateWithEntity = Modifier.applyEntity(
contentState,
new SelectionState({
anchorKey: blockKey,
anchorOffset: start,
focusKey: blockKey,
focusOffset: match.index + match[0].length
}),
entityKey
);
editorState = EditorState.push(editorState, contentStateWithEntity, 'apply-entity');
}
}
});
this.setState({ editorState });
HI @jo-asakura
I'm using DraftJSPlugins which abstracts me from having to create the entities manually. It has one plugin for links.
I share a snippet of code in case it helps.
import React from 'react';
import { EditorState, ContentState } from 'draft-js';
import Editor from 'draft-js-plugins-editor';
import createLinkifyPlugin from 'draft-js-linkify-plugin';
state = {
editorState: EditorState.createEmpty()
};
const linkPlugin = createLinkifyPlugin({
target: '_blank',
theme: {
link: 'link'
}
});
onEditorChange = (editorState) => {
this.setState({ editorState }, () => {
this.loadLink(parseLinksContent(editorState));
});
};
<Editor
editorState={this.state.editorState}
plugins={[linkPlugin]}
onChange={this.onEditorChange}
placeholder="" />
parseLinksContent.js
'use strict';
import _ from 'lodash';
import React from 'react';
import linkifyIt from 'linkify-it';
import tlds from 'tlds';
const linkify = linkifyIt();
linkify.tlds(tlds);
export default (editorState) => {
let links = [];
editorState.getCurrentContent().getBlockMap().forEach(block => {
const matches = linkify.match(block.get('text'));
if (typeof matches !== 'undefined' && matches !== null) {
for (let i = 0; i < matches.length; i++) {
links.push(_.trimEnd(matches[i].url, '/'));
}
}
});
return links;
}
@albertomr86, thank you for sharing! I was going to use linkify from DraftJSPlugins but what stopped me is it uses decorators to visually decorate links and there are no entities I can export/import. Without entities I don't know how to make sure that editable content looks similar on the web and mobile platforms.
@jo-asakura Check the example of custom rendering to apply themes.
const linkifyPlugin = createLinkifyPlugin({
component: (props) => (
// eslint-disable-next-line no-alert, jsx-a11y/anchor-has-content
<a {...props} onClick={() => alert('Clicked on Link!')} />
)
});
const plugins = [linkifyPlugin];
Maybe you could provide your own class (React.Component) and decide the behavior depending on the device.
@albertomr86, yeah this works great on the web but not on mobile because we don't use draft on mobile. The flow we use is:
Web:
* on create, Draft AST -> custom AST;
* on edit, custom AST -> Draft AST (actual edit) -> custom AST
Mobile:
* custom AST
I see at least two issues with using linkify:
Draft AST -> custom AST) I'll have to manually scan the content and extract links and their positions.custom AST -> Draft AST) linkify might detect links that are different from what was created before. This is why having properly defined ranges and entities is much better solution.@jo-asakura So what is the code you've ended up with? I have similar issue and what I tried to do is to watch on state changes, detect patterns and create or remove link (or autolink) after each change. But it was quite heavy and complicated in logic (it have to work seamlessly with links created manually). Did you find more elegant approach?
@erykpiast, unfortunately, this is exactly what I did. I watch for all the changes in several places and manually construct/modify/destroy links (initially I did all the processing inside of onChange but the performance was awful).
To simplify things it looks like this:
handleBeforeInput I watch for new characters and check if the cursor position falls inside of the already created link entity range. If there is no entity then simply check if focus word + new characters make a link, if so create a new link entity. If there is an existing entity check if entity + new characters make a link, if so append to the existing link, if not break the link and append plain text characters to it.handleKeyCommand I handle backspaces and deletes. Similar functionality to the one I have for handleBeforeInput but instead of adding text I remove it. Don't forget to check the cursor position because it can be in the end, in the start, or in the middle 馃槃 handlePastedText I create content state out of pasted text (ContentState.createFromText) and merge it with the current state using Modifier.replaceWithFragment. Also, before I merge blocks I save original block keys, so I can get the keys diff and process just newly created blocks (some sort of a speed improvement). Then I run links detection algorithm on the block keys I need.It's not heavily tested yet and I'll have more changes coming in but for now that's what I have.
Hey @jo-asakura, those are exactly the things I'm trying to do in my linkify plugin. However, it's turning out to be a non-trivial plugin :), do you have this plugin code published somewhere that's free to use?
@albertomr86, I'm curios if you've faced same problem as I do right now. I'm using similar approach to detect links but using regex instead of
linkifymodule. The problem is when link entity gets created it continues to more characters if user keeps typing. And it makes sense in some cases because the link could bewww.google.c, orwww.google.co, orwww.google.com. But what to do if user types a space or any other non link related character, how to stop the link entity and fallback to plain text?
The logic I use and the problem is in
if (!entityKeyStart) {but how would you do it otherwise, recreate entities all over again on eachonChangedoesn't seem like a good idea:const RE_URL = new RegExp(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g); editorState .getCurrentContent() .getBlockMap() .map((block) => { let contentState = editorState.getCurrentContent(); const blockKey = block.getKey(); const blockText = block.getText(); let match = null; while (match = RE_URL.exec(blockText)) { const start = match.index; const entityKeyStart = block.getEntityAt(start); if (!entityKeyStart) { contentState = contentState.createEntity('LINK', 'MUTABLE', { href: match[0] }); const entityKey = contentState.getLastCreatedEntityKey(); const contentStateWithEntity = Modifier.applyEntity( contentState, new SelectionState({ anchorKey: blockKey, anchorOffset: start, focusKey: blockKey, focusOffset: match.index + match[0].length }), entityKey ); editorState = EditorState.push(editorState, contentStateWithEntity, 'apply-entity'); } } }); this.setState({ editorState });
@albertomr86, I'm curios if you've faced same problem as I do right now. I'm using similar approach to detect links but using regex instead of
linkifymodule. The problem is when link entity gets created it continues to more characters if user keeps typing. And it makes sense in some cases because the link could bewww.google.c, orwww.google.co, orwww.google.com. But what to do if user types a space or any other non link related character, how to stop the link entity and fallback to plain text?
The logic I use and the problem is in
if (!entityKeyStart) {but how would you do it otherwise, recreate entities all over again on eachonChangedoesn't seem like a good idea:const RE_URL = new RegExp(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g); editorState .getCurrentContent() .getBlockMap() .map((block) => { let contentState = editorState.getCurrentContent(); const blockKey = block.getKey(); const blockText = block.getText(); let match = null; while (match = RE_URL.exec(blockText)) { const start = match.index; const entityKeyStart = block.getEntityAt(start); if (!entityKeyStart) { contentState = contentState.createEntity('LINK', 'MUTABLE', { href: match[0] }); const entityKey = contentState.getLastCreatedEntityKey(); const contentStateWithEntity = Modifier.applyEntity( contentState, new SelectionState({ anchorKey: blockKey, anchorOffset: start, focusKey: blockKey, focusOffset: match.index + match[0].length }), entityKey ); editorState = EditorState.push(editorState, contentStateWithEntity, 'apply-entity'); } } }); this.setState({ editorState });
@jo-asakura
Hi there... 4 years later! Any updates on this? Thanks =)
Double ping @jo-asakura! :)
Most helpful comment
@erykpiast, unfortunately, this is exactly what I did. I watch for all the changes in several places and manually construct/modify/destroy links (initially I did all the processing inside of
onChangebut the performance was awful).To simplify things it looks like this:
handleBeforeInputI watch for new characters and check if the cursor position falls inside of the already created link entity range. If there is no entity then simply check if focus word + new characters make a link, if so create a new link entity. If there is an existing entity check if entity + new characters make a link, if so append to the existing link, if not break the link and append plain text characters to it.handleKeyCommandI handlebackspaces anddeletes. Similar functionality to the one I have forhandleBeforeInputbut instead of adding text I remove it. Don't forget to check the cursor position because it can be in the end, in the start, or in the middle 馃槃handlePastedTextI create content state out of pasted text (ContentState.createFromText) and merge it with the current state usingModifier.replaceWithFragment. Also, before I merge blocks I save original block keys, so I can get the keys diff and process just newly created blocks (some sort of a speed improvement). Then I run links detection algorithm on the block keys I need.It's not heavily tested yet and I'll have more changes coming in but for now that's what I have.