将 target="_blank" 添加到 Draft.js 内容中的所有链接

Add target="_blank" to all links in Draft.js content

我正在尝试将 target="_blank" 添加到 Draft.js 内容中的所有 link。我是这个库的新手,所以我最初的尝试是简单地遍历所有实体并识别 LINK 个实体。但是,即使内容中有 link,实体映射也会变成空的。这是我的代码:

getHtml = () => {
    const contentState = this.state.editorState.getCurrentContent();

    // entityMap shows as empty
    const entityMap = contentState.getEntityMap();
    console.log('entityMap', JSON.stringify(entityMap, null, 4));

    // stateToHTML() exports the anchor tag with href, but not target="_blank"
    return stateToHTML(contentState);
};

如何遍历所有实体以及如何在找到 LINK 实体时插入 target="_blank"

P.S。我正在使用 Draft.js.

的 0.10.5 版

Link Draft.js 中的实体由 Draft.js decorators 实现。

例如从官方仓库查看the code of link-editor example

const decorator = new CompositeDecorator([
  {
    strategy: findLinkEntities,
    component: Link, // <== !!!
  },
]);

this.state = {
  editorState: EditorState.createEmpty(decorator),
  showURLInput: false,
  urlValue: '',
};

这里我们定义了匹配 link 实体的装饰器,并将 Link 组件传递给适当的 属性.

Here - 该组件的代码:

const Link = (props) => {
  const {url} = props.contentState.getEntity(props.entityKey).getData();
  return (
    <a href={url} style={styles.link}>
      {props.children}
    </a>
  );
};

所以你只需要为 a 标签添加 target="_blank"。在这种情况下,所有 link 个实体都将使用此属性呈现。

检查工作演示:

'use strict';

const {
  convertToRaw,
  CompositeDecorator,
  ContentState,
  Editor,
  EditorState,
  RichUtils,
} = Draft;

class LinkEditorExample extends React.Component {
  constructor(props) {
    super(props);

    const decorator = new CompositeDecorator([
      {
        strategy: findLinkEntities,
        component: Link,
      },
    ]);

    this.state = {
      editorState: EditorState.createEmpty(decorator),
      showURLInput: false,
      urlValue: '',
    };

    this.focus = () => this.refs.editor.focus();
    this.onChange = (editorState) => this.setState({editorState});
    this.logState = () => {
      const content = this.state.editorState.getCurrentContent();
      console.log(convertToRaw(content));
    };

    this.promptForLink = this._promptForLink.bind(this);
    this.onURLChange = (e) => this.setState({urlValue: e.target.value});
    this.confirmLink = this._confirmLink.bind(this);
    this.onLinkInputKeyDown = this._onLinkInputKeyDown.bind(this);
    this.removeLink = this._removeLink.bind(this);
  }

  _promptForLink(e) {
    e.preventDefault();
    const {editorState} = this.state;
    const selection = editorState.getSelection();
    if (!selection.isCollapsed()) {
      const contentState = editorState.getCurrentContent();
      const startKey = editorState.getSelection().getStartKey();
      const startOffset = editorState.getSelection().getStartOffset();
      const blockWithLinkAtBeginning = contentState.getBlockForKey(startKey);
      const linkKey = blockWithLinkAtBeginning.getEntityAt(startOffset);

      let url = '';
      if (linkKey) {
        const linkInstance = contentState.getEntity(linkKey);
        url = linkInstance.getData().url;
      }

      this.setState({
        showURLInput: true,
        urlValue: url,
      }, () => {
        setTimeout(() => this.refs.url.focus(), 0);
      });
    }
  }

  _confirmLink(e) {
    e.preventDefault();
    const {editorState, urlValue} = this.state;
    const contentState = editorState.getCurrentContent();
    const contentStateWithEntity = contentState.createEntity(
      'LINK',
      'MUTABLE',
      {url: urlValue}
    );
    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
    const newEditorState = EditorState.set(editorState, { currentContent: contentStateWithEntity });
    this.setState({
      editorState: RichUtils.toggleLink(
        newEditorState,
        newEditorState.getSelection(),
        entityKey
      ),
      showURLInput: false,
      urlValue: '',
    }, () => {
      setTimeout(() => this.refs.editor.focus(), 0);
    });
  }

  _onLinkInputKeyDown(e) {
    if (e.which === 13) {
      this._confirmLink(e);
    }
  }

  _removeLink(e) {
    e.preventDefault();
    const {editorState} = this.state;
    const selection = editorState.getSelection();
    if (!selection.isCollapsed()) {
      this.setState({
        editorState: RichUtils.toggleLink(editorState, selection, null),
      });
    }
  }

  render() {
    let urlInput;
    if (this.state.showURLInput) {
      urlInput =
        <div style={styles.urlInputContainer}>
          <input
            onChange={this.onURLChange}
            ref="url"
            style={styles.urlInput}
            type="text"
            value={this.state.urlValue}
            onKeyDown={this.onLinkInputKeyDown}
            />
          <button onMouseDown={this.confirmLink}>
            Confirm
          </button>
        </div>;
    }

    return (
      <div style={styles.root}>
        <div style={{marginBottom: 10}}>
          Select some text, then use the buttons to add or remove links
          on the selected text.
        </div>
        <div style={styles.buttons}>
          <button
            onMouseDown={this.promptForLink}
            style={{marginRight: 10}}>
            Add Link
          </button>
          <button onMouseDown={this.removeLink}>
            Remove Link
          </button>
        </div>
        {urlInput}
        <div style={styles.editor} onClick={this.focus}>
          <Editor
            editorState={this.state.editorState}
            onChange={this.onChange}
            placeholder="Enter some text..."
            ref="editor"
            />
        </div>
        <input
          onClick={this.logState}
          style={styles.button}
          type="button"
          value="Log State"
          />
      </div>
    );
  }
}

function findLinkEntities(contentBlock, callback, contentState) {
  contentBlock.findEntityRanges(
    (character) => {
      const entityKey = character.getEntity();
      return (
        entityKey !== null &&
        contentState.getEntity(entityKey).getType() === 'LINK'
      );
    },
    callback
  );
}

const Link = (props) => {
  const {url} = props.contentState.getEntity(props.entityKey).getData();
  return (
    <a href={url} target="_blank" style={styles.link}>
      {props.children}
    </a>
  );
};

const styles = {
  root: {
    fontFamily: '\'Georgia\', serif',
    padding: 20,
    width: 600,
  },
  buttons: {
    marginBottom: 10,
  },
  urlInputContainer: {
    marginBottom: 10,
  },
  urlInput: {
    fontFamily: '\'Georgia\', serif',
    marginRight: 10,
    padding: 3,
  },
  editor: {
    border: '1px solid #ccc',
    cursor: 'text',
    minHeight: 80,
    padding: 10,
  },
  button: {
    marginTop: 10,
    textAlign: 'center',
  },
  link: {
    color: '#3b5998',
    textDecoration: 'underline',
  },
};

ReactDOM.render(
  <LinkEditorExample />,
  document.getElementById('react-root')
);
body {
  font-family: Helvetica, sans-serif;
}

.public-DraftEditor-content {
  border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/draft-js/0.7.0/Draft.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/draft-js/0.10.0/Draft.js"></script>
<div id="react-root"></div>

draft-js-export-html stateToHTML() 允许您传递选项参数来更改实体对象的形状。如果您可以将 target='_blank' 添加到所有锚标记,您可以这样做:

...
let options = {
  entityStyleFn: (entity) => {
    const entityType = entity.get('type').toLowerCase();
    if (entityType === 'link') {
      const data = entity.getData();
      return {
        element: 'a',
        attributes: {
          href: data.url,
          target:'_blank'
        },
        style: {
          // Put styles here...
        },
      };
    } 
  }
};
return stateToHTML(contentState, options);

如果你使用了draft-js-anchor-plugin,你只能在展示内容的时候加上linkTarget属性。代码如下。

const linkPlugin = createLinkPlugin({linkTarget: '_blank'});