目录

  • 关于在TinyMCE中使用注释的指导,请参阅:使用注释。

  • 关于TinyMCE注释插件的概述,请参阅:概述。

  • 关于配置注释插件的说明,请参阅:开始使用注释插件 - 选择模式。

概述

此插件仅适用于付费的TinyMCE订阅。

注释插件允许用户通过直接在TinyMCE编辑器中添加注释来发起和参与对话。它还支持查看文档中的所有注释,以便高效协作。

交互式示例

以下演示使用的是注释插件的默认模式——回调模式。有关嵌入式模式的演示,请参阅嵌入式模式交互式示例。

HTML

<textarea id="comments-callback">
  <h2>欢迎使用Tiny注释!</h2>
  <p>请尝试我们Tiny注释高级插件的这个演示。</p>
  <ol>
    <li>高亮你想要评论的内容。</li>
    <li>点击工具栏中的<em>添加注释</em>图标。</li>
    <li>在注释侧边栏底部的文本字段中输入你的注释。</li>
    <li>点击<strong>注释</strong>。</li>
  </ol>
  <p>你的注释<span class="mce-annotation tox-comment" data-mce-annotation-uid="mce-conversation_19679600221621399703915" data-mce-annotation="tinycomments">then</span>会附加到文本上,<span class="mce-annotation tox-comment" data-mce-annotation-uid="mce-conversation_420304606321716900864126" data-mce-annotation="tinycomments">exactly like this!</span></p>
  <p>如果你想在自己的环境中试用Tiny注释,Tiny注释是你可以在注册Tiny账户后免费试用14天的高级插件之一。也请务必查看我们的文档。</p>
  <h2>一个简单的表格供你操作</h2>
  <table style="border-collapse: collapse; width: 100%;" border="1">
    <thead>
      <tr>
        <th>产品</th>
        <th>值</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><a href="https://www.tiny.cloud/">Tiny Cloud</a></td>
        <td>将强大的富文本编辑功能集成到你的应用程序中最简单、最可靠的方法。</td>
      </tr>
      <tr>
        <td><a href="https://www.tiny.cloud/drive/">Tiny Drive</a></td>
        <td>云端的TinyMCE图像和文件管理。</td>
      </tr>
    </tbody>
  </table>
  <p>感谢支持TinyMCE!我们希望它能帮助用户创建出色的内容。</p>
</textarea>

JS

const API_URL = 'https://demouserdirectory.tiny.cloud/v1/users';

const user_id = 'james-wilson';
const collaborator_id = 'mia-andersson';

const now = new Date();
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString();
const anhourago = new Date(now.getTime() - 60 * 60 * 1000).toISOString();
const fakeDelay = 200;

const randomString = () => {
  return crypto.getRandomValues(new Uint32Array(1))[0].toString(36).substring(2, 14);
};

const conversationDb = {
  'mce-conversation_19679600221621399703915': {
    uid: 'mce-conversation_19679600221621399703915',
    comments: [{
      uid: 'mce-conversation_19679600221621399703915',
      author: user_id,
      authorName: 'James Wilson',
      authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
      content: `What do you think about this?`,
      createdAt: yesterday,
      modifiedAt: yesterday
    }, {
      uid: 'mce-conversation_19679600221621399703917',
      author: collaborator_id,
      authorName: 'Mia Andersson',
      authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_mia-andersson_128_e6f9424b.jpg',
      content: `I think this is a great idea!`,
      createdAt: anhourago,
      modifiedAt: anhourago,
    }]
  },
  'mce-conversation_420304606321716900864126': {
    uid: 'mce-conversation_420304606321716900864126',
    comments: [{
      uid: 'mce-conversation_420304606321716900864126',
      author: collaborator_id,
      authorName: 'Mia Andersson',
      authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_mia-andersson_128_e6f9424b.jpg',
      content: `Please revise this sentence, exclamation points are unprofessional!`,
      createdAt: yesterday,
      modifiedAt: anhourago
    }]
  }
};

const tinycomments_create = (req, done, fail) => {
    if (req.content === 'fail') {
      fail(new Error('Something has gone wrong...'));
    } else {
      const uid = 'annotation-' + randomString();
      conversationDb[uid] = {
        uid,
        comments: [{
          uid,
          author: user_id,
          authorName: 'James Wilson',
          authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
          content: req.content,
          createdAt: req.createdAt,
          modifiedAt: req.createdAt
        }]
      };
      setTimeout(() => done({ conversationUid: uid }), fakeDelay);
    }
  };

const tinycomments_reply = (req, done) => {
  const commentUid = 'annotation-' + randomString();
  conversationDb[req.conversationUid].comments.push({
    uid: commentUid,
    author: user_id,
    authorName: 'James Wilson',
    authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
    content: req.content,
    createdAt: req.createdAt,
    modifiedAt: req.createdAt
  });
  setTimeout(() => done({ commentUid: commentUid }), fakeDelay);
};

const tinycomments_delete = (req, done) => {
  if (user_id === collaborator_id) { // 用你自己的逻辑替换,例如检查用户是否创建了对话
    delete conversationDb[req.conversationUid];
    setTimeout(() => done({ canDelete: true }), fakeDelay);
  } else {
    setTimeout(() => done({ canDelete: false, reason: 'Must be admin user' }), fakeDelay);
  }
};

const tinycomments_resolve = (req, done) => {
  const conversation = conversationDb[req.conversationUid];
  if (user_id === conversation.comments[0].author) { // 用你自己的逻辑替换,例如检查用户是否有管理员权限
    delete conversationDb[req.conversationUid];
    setTimeout(() => done({ canResolve: true }), fakeDelay);
  } else {
    setTimeout(() => done({ canResolve: false, reason: 'Must be conversation author' }), fakeDelay);
  }
};

const tinycomments_delete_comment = (req, done) => {
  const oldcomments = conversationDb[req.conversationUid].comments;
  let reason = 'Comment not found';

  const newcomments = oldcomments.filter((comment) => {
    if (comment.uid === req.commentUid) { // 找到要删除的评论
      if (user_id === comment.author) { // 用你自己的逻辑替换,例如检查用户是否有管理员权限
        return false; // 删除该评论
      } else {
        reason = 'Not authorised to delete this comment'; // 更新原因
      }
    }
    return true; // 保留该评论
  });

  if (newcomments.length === oldcomments.length) {
    setTimeout(() => done({ canDelete: false, reason }), fakeDelay);
  } else {
    conversationDb[req.conversationUid].comments = newcomments;
    setTimeout(() => done({ canDelete: true }), fakeDelay);
  }
};

const tinycomments_edit_comment = (req, done) => {
  const oldcomments = conversationDb[req.conversationUid].comments;
  let reason = 'Comment not found';
  let canEdit = false;

  const newcomments = oldcomments.map((comment) => {
    if (comment.uid === req.commentUid) { // 找到要删除的评论
      if (user_id === comment.author) { // 用你自己的逻辑替换,例如检查用户是否有管理员权限
        canEdit = true; // 用户可以编辑该评论
        return { ...comment, content: req.content, modifiedAt: new Date().toISOString() }; // 更新该评论
      } else {
        reason = 'Not authorised to edit this comment'; // 更新原因
      }
    }
    return comment; // 保留该评论
  });

  if (canEdit) {
    conversationDb[req.conversationUid].comments = newcomments;
    setTimeout(() => done({ canEdit }), fakeDelay);
  } else {
    setTimeout(() => done({ canEdit, reason }), fakeDelay);
  }
};

const tinycomments_delete_all = (req, done) => {
  const conversation = conversationDb[req.conversationUid];
  if (user_id === conversation.comments[0].author) { // 用你自己的逻辑替换,例如检查用户是否有管理员权限
    delete conversationDb[req.conversationUid];
    setTimeout(()
=> done({ canDelete: true }), fakeDelay); } else { setTimeout(() => done({ canDelete: false, reason: 'Must be conversation author' }), fakeDelay); }};const tinycomments_lookup = (req, done) => { setTimeout(() => { done({ conversation: { uid: conversationDb[req.conversationUid].uid, comments: [...conversationDb[req.conversationUid].comments] } }); }, fakeDelay);};const tinycomments_fetch = (conversationUids, done) => { const fetchedConversations = {}; conversationUids.forEach((uid) => { const conversation = conversationDb[uid]; if (conversation) { fetchedConversations[uid] = {...conversation}; } }); setTimeout(() => done({ conversations: fetchedConversations }), fakeDelay);};tinymce.init({ selector: 'textarea#comments-callback', toolbar: 'addcomment showcomments code | bold italic underline', menubar: 'file edit view insert format tools tc help', menu: { tc: { title: 'TinyComments', items: 'addcomment showcomments deleteallconversations' } }, plugins: [ 'tinycomments', 'help', 'code', 'quickbars', 'link', 'lists', 'image' ], quickbars_selection_toolbar: 'alignleft aligncenter alignright | addcomment showcomments', quickbars_image_toolbar: 'alignleft aligncenter alignright | rotateleft rotateright | imageoptions', tinycomments_mode: 'callback', tinycomments_mentions_enabled: true, sidebar_show: 'showcomments', tinycomments_create, tinycomments_reply, tinycomments_delete, tinycomments_resolve, tinycomments_delete_all, tinycomments_lookup, tinycomments_delete_comment, tinycomments_edit_comment, tinycomments_fetch, user_id, fetch_users: (userIds) => Promise.all(userIds .map((userId) => fetch(`${API_URL}/${userId}`) .then((response) => response.json()) .catch(() => ({ id: userId }))))});

在内容中无缝协作

通过 Comments 插件,用户可以:

  • 通过直观的界面创建和回复评论,实现内容协作。

  • 控制删除或解决单个评论或整个评论线程的权限。

Comments 插件的主要功能

Comments 插件使用户能够:

  • 创建评论

  • 编辑评论

  • 回复评论

  • 搜索评论

  • 解决评论线程

  • 删除评论或整个线程

支持的内容

  • 文本

  • 区块

Comments 插件在 TinyMCE 6.1 及更高版本中支持以下功能:

开始使用 Comments 插件 - 选择模式

Comments 插件提供两种模式:

回调模式

此默认模式将评论存储在外部(如数据库中),需要使用回调函数管理评论数据。设置详情请参阅:在回调模式下配置 Comments 插件。

嵌入模式

在此模式下,评论直接存储在内容中,无需回调配置。设置详情请参阅:在嵌入模式下配置 Comments 插件。

只有当 TinyMCE 以经典模式运行时才支持 Comments 插件。Comments 插件需要侧边栏,而侧边栏在内联或无干扰模式下不受支持。有关编辑模式差异的更多信息,请参阅 内联编辑模式。