# 리치 텍스트 내부 언뜻 보기에 Wagtail의 리치 텍스트 기능은 편집자에게 HTML 콘텐츠 블록에 대한 직접적인 제어권을 제공하는 것처럼 보입니다. 실제로는 여러 가지 이유로 편집자에게 최종 HTML 출력과 몇 단계 떨어진 리치 텍스트 콘텐츠 표현을 제공해야 합니다. - 편집기 인터페이스는 특정 종류의 원치 않는 마크업을 필터링해야 합니다. 여기에는 악성 스크립팅, 외부 워드 프로세서에서 붙여넣은 글꼴 스타일, 사이트 디자인의 유효성 또는 일관성을 깨뜨릴 수 있는 요소(예: 페이지는 일반적으로 `

` 요소를 페이지 제목으로 예약하므로 사용자가 리치 텍스트를 통해 자체 추가 `

` 요소를 삽입하는 것은 부적절함)가 포함됩니다. - 리치 텍스트 필드는 필드에서 허용되는 요소를 추가로 제한하기 위해 `features` 인수를 지정할 수 있습니다. - [리치 텍스트 기능](rich_text_features)을 참조하십시오. - HTML의 하위 집합을 강제하면 프레젠테이션 마크업을 데이터베이스에서 제외하여 사이트 유지 관리를 용이하게 하고, 사이트 콘텐츠를 재활용하기 쉽게 만듭니다(잠재적으로 [LaTeX](https://www.latex-project.org/)와 같은 비HTML 출력을 생성하는 것을 포함). - 페이지 링크 및 이미지와 같은 요소는 최종 HTML 표현에 없는 페이지 또는 이미지 ID와 같은 메타데이터를 보존해야 합니다. 이를 위해서는 리치 텍스트 콘텐츠가 여러 유효성 검사 및 변환 단계를 거쳐야 합니다. 편집기 인터페이스와 데이터베이스에 저장된 버전 사이, 그리고 데이터베이스 표현에서 최종 렌더링된 HTML로의 변환이 모두 포함됩니다. 이러한 이유로 Wagtail의 리치 텍스트 처리를 확장하여 새 요소를 지원하는 것은 단순히 (예를 들어) "`
` 요소를 활성화"라고 말하는 것보다 더 복잡합니다. Wagtail의 다양한 구성 요소(클라이언트 및 서버 측 모두)가 해당 기능을 처리하는 방법에 대해 동의해야 하기 때문입니다. 여기에는 편집기 인터페이스에서 어떻게 노출되어야 하는지, 데이터베이스 내에서 어떻게 표현되어야 하는지, 그리고 (적절한 경우) 프런트엔드에서 렌더링될 때 어떻게 번역되어야 하는지가 포함됩니다. Wagtail의 리치 텍스트 처리에 관련된 구성 요소는 아래에 설명되어 있습니다. ## 데이터 형식 리치 텍스트 데이터([RichTextField](rich_text_field) 및 [StreamField](../topics/streamfield) 내의 `RichTextBlock` 에서 처리됨)는 HTML과 유사하지만 동일하지는 않은 형식으로 데이터베이스에 저장됩니다. 예를 들어, 페이지 링크는 다음과 같이 저장될 수 있습니다. ```html

Contact us for more information.

``` 여기서 `linktype` 속성은 태그를 다시 작성하는 데 사용될 규칙을 식별합니다. `|richtext` 필터([리치 텍스트 필터](rich_text_filter) 참조)를 통해 템플릿에 렌더링될 때, 이것은 유효한 HTML로 변환됩니다. ```html

Contact us for more information.

``` `RichTextBlock` 의 경우, 블록의 값은 문자열로 렌더링될 때 이 변환을 자동으로 수행하는 `RichText` 객체이므로 `|richtext` 필터는 필요하지 않습니다. 마찬가지로, 리치 텍스트 콘텐츠 내의 이미지는 다음과 같이 저장될 수 있습니다. ```html ``` 이는 렌더링될 때 `img` 요소로 변환됩니다. ```html A pied wagtail ``` 다시 말하지만, `embedtype` 속성은 태그를 다시 작성하는 데 사용될 규칙을 식별합니다. `` 및 `` 를 제외한 모든 태그는 변환된 HTML에서 변경되지 않습니다. `` 및 `` 태그에는 변환을 문자열 대체로 효율적으로 수행할 수 있도록 몇 가지 추가 제약 조건이 적용됩니다. - 태그 이름과 속성은 소문자여야 합니다. - 속성 값은 이중 따옴표로 묶어야 합니다. - `embed` 요소는 XML 자체 닫는 태그 구문(닫는 `` 태그 대신 `/>` 로 끝나는 태그)을 사용해야 합니다. - 속성 값에 허용되는 유일한 HTML 엔티티는 `<`, `>`, `&` 및 `"` 입니다. (rich_text_manual_conversion)= ### 수동 변환 `RichTextBlock` 또는 `|richtext` 필터가 적절하지 않은 시나리오의 경우, Wagtail의 `expand_db_html` 유틸리티 함수를 직접 사용하십시오. 이 함수는 CMS 콘텐츠(이미지, 임베드, 문서, 페이지 링크)에 대한 내부 참조를 확장하여 표시할 준비가 된 HTML을 생성합니다. ```python from wagtail.rich_text import expand_db_html # 저장된 리치 텍스트 데이터 형식을 렌더링에 적합한 HTML로 변환합니다. expand_db_html(page.body) ``` ## 기능 레지스트리 프로젝트 내의 모든 앱은 새 `linktype` 및 `embedtype` 규칙과 같이 Wagtail의 리치 텍스트 처리에 대한 확장을 정의할 수 있습니다. _기능 레지스트리_라고 하는 객체는 리치 텍스트가 어떻게 동작해야 하는지에 대한 중앙 소스 역할을 합니다. 이 객체는 시작 시 리치 텍스트와 관련된 모든 정의를 수집하기 위해 호출되는 [리치 텍스트 기능 등록](register_rich_text_features) 후크를 통해 액세스할 수 있습니다. ```python # my_app/wagtail_hooks.py from wagtail import hooks @hooks.register('register_rich_text_features') def register_my_feature(features): # 여기에 새 정의를 'features'에 추가합니다. ``` (rich_text_rewrite_handlers)= ## 재작성 핸들러 재작성 핸들러는 `` 및 `` 와 같은 리치 텍스트 태그의 내용을 프런트엔드 HTML로 변환하는 방법을 아는 클래스입니다. 예를 들어, `PageLinkHandler` 클래스는 리치 텍스트 태그 `` 를 HTML 태그 `` 로 변환하는 방법을 알고 있습니다. 재작성 핸들러는 리치 텍스트 태그에 대한 다른 유용한 정보도 제공할 수 있습니다. 예를 들어, 적절한 태그가 주어지면 `PageLinkHandler` 를 사용하여 참조되는 페이지를 추출할 수 있습니다. 이는 리치 텍스트에서 참조되는 객체에 대한 정보가 필요한 다운스트림 코드에 유용할 수 있습니다. 새로운 `linktype` 및 `embedtype` 태그를 지원하기 위해 사용자 지정 재작성 핸들러를 만들 수 있습니다. 새 핸들러는 `wagtail.richtext.LinkHandler` 또는 `wagtail.richtext.EmbedHandler` 중 하나를 상속하는 Python 클래스여야 합니다. 새 클래스는 다음 메서드 중 적어도 일부를 재정의해야 합니다(`LinkHandler` 에 대해 여기에 나열되어 있지만 `EmbedHandler` 는 동일한 서명을 가짐). ```{eval-rst} .. class:: LinkHandler .. attribute:: identifier 필수. :code:`identifier` 속성은 이 핸들러가 처리해야 하는 리치 텍스트 태그를 나타내는 문자열입니다. 예를 들어, :code:`PageLinkHandler.identifier` 는 문자열 :code:`"page"` 로 설정되어 :code:`` 가 있는 모든 리치 텍스트 태그가 이 핸들러에 의해 처리되어야 함을 나타냅니다. .. method:: expand_db_attributes(attrs) 선택 사항. :code:`expand_db_attributes` 메서드는 데이터베이스 리치 텍스트 :code:`` 태그(:code:`EmbedHandler` 의 경우 :code:``)의 속성 사전을 가져와 유효한 프런트엔드 HTML을 생성하는 데 사용될 것으로 예상됩니다. 예를 들어, :code:`PageLinkHandler.expand_db_attributes` 는 :code:`{'id': 123}` 을 수신하여 ID가 123인 Wagtail 페이지를 검색하고 :code:`` 와 같은 URL에 대한 링크를 렌더링할 수 있습니다. 이 메서드 또는 :code:`expand_db_attributes_many` 는 사용자 지정 재작성 핸들러에 정의되어야 합니다. .. method:: expand_db_attributes_many(attrs_list) 선택 사항. :code:`expand_db_attributes_many` 메서드는 :code:`expand_db_attributes` 와 유사하게 작동하지만 속성 사전 목록을 가져와 HTML 태그 목록을 반환합니다. 이 메서드는 재작성 핸들러가 대량으로 작업하는 데 사용됩니다. 예를 들어, 여러 데이터베이스 쿼리 대신 하나의 데이터베이스 쿼리를 수행하는 기능을 활용합니다. 이 메서드 또는 :code:`expand_db_attributes` 는 사용자 지정 재작성 핸들러에 정의되어야 합니다. 정의되지 않은 경우, :code:`expand_db_attributes_many` 의 기본 구현은 :code:`expand_db_attributes` 에 대한 일련의 호출을 통해 작동합니다. .. method:: get_model() 선택 사항. 정적 :code:`get_model` 메서드는 Django 모델과 관련된 콘텐츠를 렌더링하는 데 사용되는 핸들러에만 적용됩니다. 이 메서드를 사용하면 핸들러가 처리할 수 있는 콘텐츠 유형을 노출할 수 있습니다. 예를 들어, :code:`PageLinkHandler.get_model` 은 Wagtail 클래스 :code:`Page` 를 반환합니다. Django 모델과 관련이 없는 핸들러는 이 메서드를 정의하지 않은 상태로 둘 수 있으며, 호출하면 :code:`NotImplementedError` 가 발생합니다. .. method:: get_instance(attrs) 선택 사항. 클래스 메서드 :code:`get_instance` 메서드는 Django 모델과 관련된 콘텐츠를 렌더링하는 데 사용되는 핸들러에만 적용됩니다. 이 메서드는 데이터베이스 리치 텍스트 :code:`` 태그(:code:`EmbedHandler` 의 경우 :code:``)의 속성 사전을 가져와 참조되는 특정 Django 모델 인스턴스를 반환하는 데 사용될 것으로 예상됩니다. 예를 들어, :code:`PageLinkHandler.get_instance` 는 :code:`{'id': 123}` 을 수신하여 ID가 123인 Wagtail :code:`Page` 클래스의 인스턴스를 반환할 수 있습니다. 제공된 속성을 사용하여 Django 모델 인스턴스를 검색할 수 없는 경우(예: 제공된 :code:`id` 속성이 유효하지 않은 경우) 이 메서드는 예외를 발생시켜야 합니다. 정의되지 않은 경우, 이 메서드의 기본 구현은 제공된 :code:`id` 속성을 사용하여 :code:`get_model` 에서 반환된 클래스의 :code:`id` 모델 필드를 쿼리합니다. 이는 다른 모델 필드를 사용하려는 경우 사용자 지정 핸들러에서 재정의할 수 있습니다. .. method:: get_many(attrs_list) 선택 사항. 클래스 메서드 :code:`get_many` 메서드는 :code:`get_instance` 와 유사하게 작동하지만 속성 사전 목록을 가져와 Django 모델 인스턴스 목록을 반환합니다. 검색할 수 없는 모든 인스턴스는 반환된 목록에서 :code:`None` 으로 표시됩니다. ``` 아래는 사용자 이메일 주소에 대한 리치 텍스트 링크를 지원하기 위해 이러한 메서드 중 일부를 구현하는 사용자 지정 재작성 핸들러의 예입니다. `` 과 같은 리치 텍스트 태그를 `` 와 같은 유효한 HTML로 변환하는 것을 지원합니다. 이 예는 사용자가 이러한 종류의 링크를 리치 텍스트 편집기에 삽입할 수 있도록 동등한 프런트엔드 기능이 추가되었다고 가정합니다. ```python from django.contrib.auth import get_user_model from wagtail.rich_text import LinkHandler class UserLinkHandler(LinkHandler): identifier = 'user' @staticmethod def get_model(): return get_user_model() @classmethod def get_instance(cls, attrs): model = cls.get_model() return model.objects.get(username=attrs['username']) @classmethod def expand_db_attributes(cls, attrs): user = cls.get_instance(attrs) return '' % user.email ``` ### 재작성 핸들러 등록 재작성 핸들러는 [리치 텍스트 기능 등록](register_rich_text_features) 후크를 통해 기능 레지스트리에 등록되어야 합니다. 링크 핸들러와 임베드 핸들러를 모두 등록하기 위한 독립적인 메서드가 제공됩니다. ```{eval-rst} .. method:: FeatureRegistry.register_link_type(handler) 이 메서드를 사용하면 :code:`wagtail.rich_text.LinkHandler` 에서 파생된 사용자 지정 핸들러를 등록하고, 리치 텍스트 변환 중에 사용할 수 있는 링크 핸들러 목록에 추가할 수 있습니다. ``` ```python # my_app/wagtail_hooks.py from wagtail import hooks from my_app.handlers import MyCustomLinkHandler @hooks.register('register_rich_text_features') def register_link_handler(features): features.register_link_type(MyCustomLinkHandler) ``` Wagtail의 내장 `external` 및 `email` 링크에 대한 링크 재작성 핸들러를 정의하는 것도 가능합니다. 비록 미리 정의된 `linktype` 이 없더라도 말입니다. 예를 들어, SEO 목적으로 외부 링크에 `rel="nofollow"` 속성을 추가하려면 다음과 같이 할 수 있습니다. ```python from django.utils.html import escape from wagtail import hooks from wagtail.rich_text import LinkHandler class NoFollowExternalLinkHandler(LinkHandler): identifier = 'external' @classmethod def expand_db_attributes(cls, attrs): href = attrs["href"] return '' % escape(href) @hooks.register('register_rich_text_features') def register_external_link(features): features.register_link_type(NoFollowExternalLinkHandler) ``` 마찬가지로, `email` linktype을 사용하여 이메일 링크에 대한 사용자 지정 재작성 핸들러를 추가할 수 있습니다(예: 리치 텍스트에서 이메일 난독화). ```{eval-rst} .. method:: FeatureRegistry.register_embed_type(handler) 이 메서드를 사용하면 :code:`wagtail.rich_text.EmbedHandler` 에서 파생된 사용자 지정 핸들러를 등록하고, 리치 텍스트 변환 중에 사용할 수 있는 임베드 핸들러 목록에 추가할 수 있습니다. ``` ```python # my_app/wagtail_hooks.py from wagtail import hooks from my_app.handlers import MyCustomEmbedHandler @hooks.register('register_rich_text_features') def register_embed_handler(features): features.register_embed_type(MyCustomEmbedHandler) ``` ## 편집기 위젯 리치 텍스트 필드에 사용되는 편집기 인터페이스는 [WAGTAILADMIN_RICH_TEXT_EDITORS](wagtailadmin_rich_text_editors) 설정으로 구성할 수 있습니다. Wagtail은 구현을 제공합니다: `wagtail.admin.rich_text.DraftailRichTextArea`([Draft.js](https://draftjs.org/) 기반의 [Draftail](https://www.draftail.org/) 편집기). 자신만의 리치 텍스트 편집기 구현을 만드는 것이 가능합니다. 최소한 리치 텍스트 편집기는 `options` 키워드 인수(WAGTAILADMIN_RICH_TEXT_EDITORS의 `OPTIONS` 필드에서 가져온 편집기별 구성 옵션 사전)를 허용하고, 위에 설명된 HTML과 유사한 형식으로 문자열 데이터를 사용하고 생성하는 Django **_클래스_ django.forms.Widget** 서브클래스입니다. 일반적으로 리치 텍스트 위젯은 `RichTextField` / `RichTextBlock` 또는 `WAGTAILADMIN_RICH_TEXT_EDITORS` 의 `features` 옵션에서 전달되는 `features` 목록을 받습니다. 이 목록은 편집기의 해당 인스턴스에서 사용할 수 있는 기능을 정의합니다([리치 텍스트 기능](rich_text_features) 참조). 기능을 지원하려면 위젯 클래스에 `accepts_features = True` 속성을 설정하십시오. 그러면 위젯 생성자는 `features` 키워드 인수로 기능 목록을 받게 됩니다. [리치 텍스트 기능](rich_text_features)에 나열된 표준 기능 식별자 세트가 있지만, 이것이 최종 목록은 아닙니다. 기능 식별자는 규칙에 의해서만 정의되며, 각 편집기 위젯이 어떤 기능을 인식하고 그에 따라 동작을 조정할지 결정하는 것은 각 편집기 위젯에 달려 있습니다. 개별 편집기 위젯은 내장 기능으로든 편집기 위젯에 플러그인 메커니즘이 있는 경우 플러그인 메커니즘을 통해서든 기본 세트보다 적거나 많은 기능을 구현할 수 있습니다. 예를 들어, 타사 Wagtail 확장이 `table` 을 새로운 리치 텍스트 기능으로 도입하고, Draftail 편집기(플러그인 메커니즘을 제공함)에 대한 구현을 제공할 수 있습니다. 이 경우, 타사 확장은 사용자 지정 편집기 위젯을 인식하지 못하므로 위젯은 `table` 기능 식별자를 처리하는 방법을 알지 못합니다. 편집기 위젯은 인식하지 못하는 기능 식별자를 자동으로 무시해야 합니다. 기능 레지스트리의 `default_features` 속성은 `RichTextField` / `RichTextBlock` 또는 `WAGTAILADMIN_RICH_TEXT_EDITORS` 에 명시적인 기능 목록이 제공되지 않은 경우 사용될 기능 식별자 목록입니다. 이 목록은 `register_rich_text_features` 후크 내에서 수정하여 새 기능을 기본적으로 활성화하고, `get_default_features()` 를 호출하여 검색할 수 있습니다. ```python @hooks.register('register_rich_text_features') def make_h1_default(features): features.default_features.append('h1') ``` `register_rich_text_features` 후크 외부(예: 위젯 클래스 내부)에서는 기능 레지스트리를 `wagtail.rich_text.features` 객체로 가져올 수 있습니다. 기능 지원이 가능한 리치 텍스트 편집기의 가능한 시작점은 다음과 같습니다. ```python from django.forms import widgets from wagtail.rich_text import features class CustomRichTextArea(widgets.TextArea): accepts_features = True def __init__(self, *args, **kwargs): self.options = kwargs.pop('options', None) self.features = kwargs.pop('features', None) if self.features is None: self.features = features.get_default_features() super().__init__(*args, **kwargs) ``` ## 편집기 플러그인 ```{eval-rst} .. method:: FeatureRegistry.register_editor_plugin(editor_name, feature_name, plugin_definition) 리치 텍스트 편집기는 새로운 기능을 사용하여 편집기를 확장할 수 있도록 플러그인 메커니즘을 제공하는 경우가 많습니다. :code:`register_editor_plugin` 메서드는 :code:`register_rich_text_features` 후크가 주어진 리치 텍스트 기능이 활성화될 때 편집기에 가져올 플러그인을 정의하는 표준화된 방법을 제공합니다. :code:`register_editor_plugin` 은 편집기 이름(편집기 위젯을 고유하게 식별하는 문자열 - Wagtail은 내장 편집기에 :code:`draftail` 식별자를 사용함), 기능 식별자, 플러그인 정의 객체를 전달받습니다. 이 객체는 편집기 위젯에 특화되어 있으며 임의의 값일 수 있지만, 일반적으로 플러그인의 JavaScript 코드를 참조하는 :doc:`Django 폼 미디어 ` 정의(편집기 위젯 자체의 미디어 정의에 병합됨)와 편집기를 인스턴스화할 때 전달될 관련 구성 옵션을 포함합니다. .. method:: FeatureRegistry.get_editor_plugin(editor_name, feature_name) 편집기 위젯 내에서 주어진 기능에 대한 플러그인 정의는 편집기 자체의 식별자 문자열과 기능 식별자를 전달하여 :code:`get_editor_plugin` 메서드를 통해 검색할 수 있습니다. 일치하는 플러그인이 등록되지 않은 경우 :code:`None` 을 반환합니다. Wagtail의 내장 편집기에 대한 플러그인 형식에 대한 자세한 내용은 :doc:`./extending_draftail` 을 참조하십시오. ``` (rich_text_format_converters)= ## 형식 변환기 편집기 위젯은 Wagtail의 리치 텍스트 형식과 직접 작동할 수 없으며, 자체 기본 형식으로 변환해야 하는 경우가 많습니다. Draftail의 경우, ContentState라고 하는 JSON 기반 형식입니다([Draft.js가 리치 텍스트 데이터를 나타내는 방법](https://rajaraodv.medium.com/how-draft-js-represents-rich-text-data-eeabb5f25cf2) 참조). HTML의 `contentEditable` 메커니즘을 기반으로 하는 편집기는 유효한 HTML을 필요로 하므로, Wagtail은 링크 및 임베드 요소에 필요한 추가 데이터가 `data-` 속성에 저장되는 "편집기 HTML"이라는 규칙을 사용합니다. 예를 들어: `Contact us`. Wagtail은 리치 텍스트 형식과 기본 편집기 형식 간의 변환을 수행하기 위해 `wagtail.admin.rich_text.converters.contentstate.ContentstateConverter` 및 `wagtail.admin.rich_text.converters.editor_html.EditorHTMLConverter` 라는 두 가지 유틸리티 클래스를 제공합니다. 이 클래스는 편집기 위젯과 독립적이며 템플릿에 리치 텍스트를 렌더링할 때 발생하는 재작성 프로세스와는 다릅니다. 두 클래스 모두 생성자에 `features` 목록을 인수로 허용하며, Wagtail 리치 텍스트 데이터를 편집기 형식으로 변환하는 `from_database_format(data)` 와 편집기 데이터를 Wagtail 리치 텍스트 형식으로 변환하는 `to_database_format(data)` 의 두 가지 메서드를 구현합니다. 편집기 플러그인과 마찬가지로 변환기 클래스의 동작은 전달된 기능 목록에 따라 달라질 수 있습니다. 특히, 출력에 현재 활성화된 기능 세트에 해당하는 HTML 요소만 포함되도록 화이트리스트 규칙을 적용할 수 있습니다. 기능 레지스트리는 `register_converter_rule` 메서드를 제공하여 `register_rich_text_features` 후크가 주어진 기능이 활성화될 때 활성화될 변환 규칙을 정의할 수 있도록 합니다. ```{eval-rst} .. method:: FeatureRegistry.register_converter_rule(converter_name, feature_name, rule_definition) :code:`register_editor_plugin` 은 변환기 이름(변환기 클래스를 고유하게 식별하는 문자열 - Wagtail은 :code:`contentstate` 및 :code:`editorhtml` 식별자를 사용함), 기능 식별자, 규칙 정의 객체를 전달받습니다. 이 객체는 변환기에 특화되어 있으며 임의의 값일 수 있습니다. :code:`contentstate` 변환기에 대한 규칙 정의 형식에 대한 자세한 내용은 :doc:`./extending_draftail` 을 참조하십시오. .. method:: FeatureRegistry.get_converter_rule(converter_name, feature_name) 변환기 클래스 내에서 주어진 기능에 대한 규칙 정의는 변환기 자체의 식별자 문자열과 기능 식별자를 전달하여 :code:`get_converter_rule` 메서드를 통해 검색할 수 있습니다. 일치하는 규칙이 등록되지 않은 경우 :code:`None` 을 반환합니다. ```