如何让两个 RichText 特性互斥

How to get two RichText features to be mutually exclusive

所以基本上我已经添加了两个自定义功能来为 RichTextBlock 的文本着色,我想让它们为一部分文本选择一个会自动取消选择另一个颜色按钮,就像它已经是h 标签的大小写。

我搜索了一下,但没找到多少,所以我想我需要一些帮助,无论是建议、说明还是代码。

我的功能是这样的:

@hooks.register('register_rich_text_features')
def register_redtext_feature(features):
    feature_name = 'redtext'
    type_ = 'RED_TEXT'
    tag = 'span'

    control = {
        'type': type_,
        'label': 'Red',
        'style': {'color': '#bd003f'},
    }

    features.register_editor_plugin(
        'draftail', feature_name, draftail_features.InlineStyleFeature(control)
    )

    db_conversion = {
        'from_database_format': {tag: InlineStyleElementHandler(type_)},
        'to_database_format': {
            'style_map': {
                type_: {'element': tag, 'props': {'class': 'text-primary'}}
            }
        },
    }

    features.register_converter_rule(
        'contentstate', feature_name, db_conversion
    )

另一件类似,但颜色不同。

这是可能的,但它需要在 Wagtail 中跳过许多环节。 h1…h6 标签开箱即用,因为它们是块级格式——编辑器中的每个块只能是一种类型。在这里,您将此 RED_TEXT 格式创建为内联格式(“内联样式”),它有意支持将多种格式应用于同一文本。


如果您无论如何都想实现这种相互排斥的实现 – 您需要编写自定义 JS 代码,以便在尝试添加新样式时自动神奇地从文本中删除所需的样式。

这里有一个函数可以做到这一点。它遍历用户选择中的所有字符,并从中删除相关样式:

/**
 * Remove all of the COLOR_ styles from the current selection.
 * This is to ensure only one COLOR_ style is applied per range of text.
 * Replicated from https://github.com/thibaudcolas/draftjs-filters/blob/f997416a0c076eb6e850f13addcdebb5e52898e5/src/lib/filters/styles.js#L7,
 * with additional "is the character in the selection" logic.
 */
export const filterColorStylesFromSelection = (
  content: ContentState,
  selection: SelectionState,
) => {
  const blockMap = content.getBlockMap();
  const startKey = selection.getStartKey();
  const endKey = selection.getEndKey();
  const startOffset = selection.getStartOffset();
  const endOffset = selection.getEndOffset();

  let isAfterStartKey = false;
  let isAfterEndKey = false;

  const blocks = blockMap.map((block) => {
    const isStartBlock = block.getKey() === startKey;
    const isEndBlock = block.getKey() === endKey;
    isAfterStartKey = isAfterStartKey || isStartBlock;
    isAfterEndKey = isAfterEndKey || isEndBlock;
    const isBeforeEndKey = isEndBlock || !isAfterEndKey;
    const isBlockInSelection = isAfterStartKey && isBeforeEndKey;

    // Skip filtering through the block chars if out of selection.
    if (!isBlockInSelection) {
      return block;
    }

    let altered = false;

    const chars = block.getCharacterList().map((char, i) => {
      const isAfterStartOffset = i >= startOffset;
      const isBeforeEndOffset = i < endOffset;
      const isCharInSelection =
        // If the selection is on a single block, the char needs to be in-between start and end offsets.
        (isStartBlock &&
          isEndBlock &&
          isAfterStartOffset &&
          isBeforeEndOffset) ||
        // Start block only: after start offset
        (isStartBlock && !isEndBlock && isAfterStartOffset) ||
        // End block only: before end offset.
        (isEndBlock && !isStartBlock && isBeforeEndOffset) ||
        // Neither start nor end: just "in selection".
        (isBlockInSelection && !isStartBlock && !isEndBlock);

      let newChar = char;

      if (isCharInSelection) {
        char
          .getStyle()
          .filter((type) => type.startsWith("COLOR_"))
          .forEach((type) => {
            altered = true;
            newChar = CharacterMetadata.removeStyle(newChar, type);
          });
      }

      return newChar;
    });

    return altered ? block.set("characterList", chars) : block;
  });

  return content.merge({
    blockMap: blockMap.merge(blocks),
  });
};

本文摘自 Draftail ColorPicker demo, which you can see running in the Draftail Storybook’s "Custom formats" example


要在 Draftail 中实现这种自定义,您需要使用 controls API. Unfortunately that API isn’t currently supported out of the box in Wagtail’s integration of the editor (see wagtail/wagtail#5580),因此目前为了使其正常工作,您还需要在 Wagtail 中自定义 Draftail 的初始化.