Report an issue

指南评论存档自定义 UI

默认情况下,评论存档下拉面板通过在工具栏配置中添加 'commentsArchive' 按钮显示在工具栏中。有关更多信息,请参阅 评论 指南。

在本指南中,您将学习如何在您的应用程序中准备一个自定义评论存档 UI,并在您选择的容器中显示它。

您应该只显示一个评论存档 UI 实例,以避免在多个位置使用相同的 DOM 元素。这意味着,除其他事项外,如果您为评论存档提供自己的自定义 UI,则不应该将 'commentsArchive' 按钮添加到工具栏配置中。

# 开始之前

在本指南中,将使用 CKEditor 云服务和 实时协作评论 功能。但是,评论功能 API 也可以以类似的方式与 独立评论 功能一起使用。

在继续之前,请确保您的编辑器已正确集成到评论功能中。

# 准备 HTML 结构

在本指南中,我们将准备一个带有自定义侧边栏的编辑器集成。

将有两个选项卡,允许用户在侧边栏中显示的内容之间切换。默认情况下,侧边栏将显示编辑器的常规宽侧边栏。另一个选项卡将切换侧边栏内容,以显示已解决的评论。

首先,通过扩展 div.editor-container__sidebar 容器来调整 HTML 结构

<div class="main-container">

    ...

    <div class="editor-container" id="editor-container">
        <div class="editor-container__editor-wrapper">

            <div class="editor-container__editor"><div id="editor"></div></div>

            <div class="editor-container__sidebar">
                <div class="tabs">
                    <button class="tabs__item active" data-target="editor-annotations">Sidebar</button>
                    <button class="tabs__item" data-target="archive">Comments archive</button>
                </div>

                <div class="sidebar active" id="editor-annotations"></div>

                <div class="sidebar sidebar--archive" id="archive">
                    <div class="comments-archive">
                        <div class="comments-archive__list"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    ...

</div>

然后,为侧边栏添加样式

<style>
    .ck.ck-toolbar_grouping {
        height: 40px;
    }

    .editor-container__sidebar {
        margin: 0;
        padding: 0;
    }

    .sidebar {
        display: none;
        font-size: 20px;
        padding: 0 10px 10px;
        border: 1px solid #ccced1;
        border-left: none;
        overflow: hidden;
        box-sizing: border-box;
        height: 0;
    }

    .sidebar.active {
        display: block;
        min-height: calc(100% - 40px);
    }

    .sidebar--archive.active {
        overflow: auto;
    }

    .comments-archive__list {
        padding: 0;
        margin: 0;
    }

    .comments-archive__list:empty:before {
        display: block;
        content: "There are no archived comment threads.";
        margin-top: 20px;
        font-size: 13px;
        text-align: center;
        font-style: italic;
        color: #555;
    }

    .comments-archive__list  > .ck-annotation-wrapper {
        margin-top: 10px;
    }

    .tabs {
        width: 100%;
        min-height: 40px;
        display: flex;
        flex-direction: row;
        align-items: center;
        border-top: 1px solid #ccced1;
    }

    .tabs__item {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        width: 100%;
        height: 100%;
        min-height: 40px;
        border: 1px solid #ccced1;
        border-width: 0 1px;
        opacity: 0.5;
        box-sizing: border-box;
        padding: 0;
    }

    .tabs__item:first-child {
        border-left: none;
    }

    .tabs__item.active {
        background: #fff;
        opacity: 1.0;
    }
</style>

# 实现自定义评论存档 UI 插件

现在,创建一个插件,它将使用提供的 HTML 结构,并使用已解决的评论线程填充评论存档容器。

选项卡的行为将作为简单的 DOM 事件监听器来实现。

您可以观察 CommentsArchiveUI#annotationViews 集合上的更改,以填充评论存档选项卡内容。

此外,为了获得更好的用户体验,只要在侧边栏中显示评论存档,常规评论线程的批注将以内联显示模式显示。这将使用户在存档打开时也能访问常规评论线程数据。

class CustomCommentsArchiveUI extends Plugin {
    static get requires() {
        // We will use a property from the `CommentsArchiveUI` plugin, so add it to requires.
        return [ 'CommentsArchiveUI' ];
    }

    init() {
        this.tabs = document.querySelectorAll( '.tabs__item' );
        this.sidebars = document.querySelectorAll( '.sidebar' );

        // Switch the side panel to the appropriate tab after clicking it.
        this.tabs.forEach( item => {
            item.addEventListener( 'click', () => this.handleTabClick( item ) );
        } );

        this.initCommentsArchive();
    }

    // Switches between the active tabs.
    // Shows appropriate tab container and set the CSS classes to reflect the changes.
    handleTabClick( tabElement ) {
        if ( tabElement.classList.contains( 'active' ) ) {
            return;
        }

        const annotationsUIs = this.editor.plugins.get( 'AnnotationsUIs' );
        const targetId = tabElement.dataset.target;
        const sidebarContainer = document.getElementById( targetId );

        this.tabs.forEach( item => {
            item.classList.remove( 'active' );
        } );

        this.sidebars.forEach( item => {
            item.classList.remove( 'active' );
        } );

        tabElement.classList.add( 'active' );
        sidebarContainer.classList.add( 'active' );

        const isCommentsArchiveOpen = targetId === 'archive';

        // If the comments archive is open, switch the display mode for comments to "inline".
        //
        // This way the annotations for regular comments threads will be displayed next to them
        // when a user clicks on the comment thread marker.
        //
        // When the comments archive is closed, switch back to displaying comments annotations in the wide sidebar.
        annotationsUIs.switchTo( isCommentsArchiveOpen ? 'inline' : 'wideSidebar' );
    }

    initCommentsArchive() {
        // Container for the resolved comment threads annotations.
        const commentsArchiveList = document.querySelector( '.comments-archive__list' );

        // The `CommentsArchiveUI` plugin handles all annotation views that can be used
        // to render resolved comment threads inside the comments archive container.
        const commentsArchiveUI = this.editor.plugins.get( 'CommentsArchiveUI' );

        // First, handle the initial resolved comment threads.
        for ( const annotationView of commentsArchiveUI.annotationViews ) {
            commentsArchiveList.appendChild( annotationView.element );
        }

        // Handler to append new resolved thread inside the comments archive custom view.
        commentsArchiveUI.annotationViews.on( 'add', ( _, annotationView ) => {
            if ( !commentsArchiveList.contains( annotationView.element ) ) {
                commentsArchiveList.appendChild( annotationView.element );
            }
        } );

        // Handler to remove the element when thread has been removed or reopened.
        commentsArchiveUI.annotationViews.on( 'remove', ( _, annotationView ) => {
            if ( commentsArchiveList.contains( annotationView.element ) ) {
                commentsArchiveList.removeChild( annotationView.element );
            }
        } );
    }
}

最后,将新插件添加到编辑器中。

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        // ...
        plugins: [
            // ...
            CustomCommentsArchiveUI
        ]
    } )
    .then( /* ... */ )
    .catch( /* ... */ );

# 演示

与您的同事共享此页面的完整 URL,以便实时协作!

点击工具栏中的“添加评论”按钮 添加评论线程,然后使用“勾号”图标 解决评论线程。最后,您可以在“评论存档”选项卡中看到已解决的评论线程。