Callback mode 是 Comments 插件的默认模式。在 callback 模式下,需要使用回调函数将用户评论保存到服务器上。Comments 函数(创建、回复、编辑、删除评论、删除所有对话、解决、查找和获取对话)的配置会因服务器端存储配置而异。

Callback 模式为与自定义后端系统集成提供了最大的灵活性。所有评论操作都通过自定义回调函数处理,这些函数与您的服务器进行通信。

Comments 插件在 callback 模式下的工作原理

所有回调函数都接受三个参数:请求对象(或 tinycomments_fetch 的数组)、done 回调和 fail 回调。函数的返回类型并不重要,但所有函数必须精确调用这两个回调中的一个:fail 或 done。

  • fail 回调接受一个特定于每个函数的参数。

  • done 回调接受一个特定于每个函数的参数,用于指示成功完成。

大多数函数(创建、回复和编辑)需要一个 id 来标识当前作者。这可以在回调中直接提供,或通过 tinycomments_fetch_author_info 选项动态提供。

当前作者:默认情况下,Comments 插件不知道当前用户的姓名。为了在创建新对话时显示正确的作者信息,集成人员可以使用 tinycomments_fetch_author_info 选项通过回调动态提供作者详细信息。

作者显示名称:authorName 字段在评论对象中是可选的。如果未提供,插件将默认使用 author 字段的值。

初始对话加载

在编辑器初始加载期间,Comments 使用 tinycomments_fetch 回调来检索文档中的现有对话。如果未配置,Comments 将回退到 tinycomments_lookup。

评论交互流程

当用户添加评论或回复时,Comments 插件使用 tinycomments_lookup 回调来检索所选对话。这确保了用户始终看到最新的对话数据。

交互式示例

HTML

<textarea id="comments-callback">
  <h2>Welcome to Tiny Comments!</h2>
  <p>Please try out this demo of our Tiny Comments premium plugin.</p>
  <ol>
    <li>Highlight the content you want to comment on.</li>
    <li>Click the <em>Add Comment</em> icon in the toolbar.</li>
    <li>Type your comment into the text field at the bottom of the Comment sidebar.</li>
    <li>Click <strong>Comment</strong>.</li>
  </ol>
  <p>Your comment is <span class="mce-annotation tox-comment" data-mce-annotation-uid="mce-conversation_19679600221621399703915" data-mce-annotation="tinycomments">then</span> attached to the text, <span class="mce-annotation tox-comment" data-mce-annotation-uid="mce-conversation_420304606321716900864126" data-mce-annotation="tinycomments">exactly like this!</span></p>
  <p>If you want to take Tiny Comments for a test drive in your own environment, Tiny Comments is one of the premium plugins you can try for free for 14 days by signing up for a Tiny account. Make sure to check out our documentation as well.</p>
  <h2>A simple table to play with</h2>
  <table style="border-collapse: collapse; width: 100%;" border="1">
    <thead>
      <tr>
        <th>Product</th>
        <th>Value</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><a href="https://www.tiny.cloud/">Tiny Cloud</a></td>
        <td>The easiest and most reliable way to integrate powerful rich text editing into your application.</td>
      </tr>
      <tr>
        <td><a href="https://www.tiny.cloud/drive/">Tiny Drive</a></td>
        <td>Image and file management for TinyMCE in the cloud.</td>
      </tr>
    </tbody>
  </table>
  <p>Thanks for supporting TinyMCE! We hope it helps your users create great content.</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 }))))
});

选项

必需的回调函数

当使用callback模式时,Comments插件需要以下操作的回调函数:

  • tinycomments_create - 创建新的评论对话

  • tinycomments_reply - 向现有对话添加回复

  • tinycomments_edit_comment - 编辑现有评论

  • tinycomments_delete_comment - 删除单个评论

  • tinycomments_delete - 删除整个对话线程

  • tinycomments_delete_all - 删除所有对话

  • tinycomments_lookup - 检索特定对话

tinycomments_create

Comments插件使用tinycomments_create函数创建评论。

tinycomments_create函数将评论保存为新的对话,并通过done回调返回唯一的对话ID。如果发生不可恢复的错误,应通过fail回调进行指示。

tinycomments_create函数的第一个参数是一个请求(req)对象,具有以下字段:

  • content: 要创建的评论内容。

  • createdAt: 评论创建的日期。

done回调应接受以下对象:

{
  conversationUid: string, // 新对话的uid
  // 可选的错误回调,当新对话无法创建时将运行
  onError: (err) => { ... },
  // 可选的成功回调,当新对话成功创建时将运行
  onSuccess: (uid) => { ... }
}
例如:
const create_comment = (ref, done, fail) => {
  const { content, createdAt } = ref;

  fetch('https://api.example/conversations/', {
    method: 'POST',
    body: JSON.stringify({ content: content, createdAt: createdAt }),
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
  })
    .then((response) => {
      if (!response.ok) {
        throw new Error('Failed to create comment');
      }
      return response.json();
    })
    .then((ref2) => {
      let conversationUid = ref2.conversationUid;
      done({ conversationUid: conversationUid });
    })
    .catch((e) => {
      fail(e);
    });
}

tinymce.init({
  selector: 'textarea', // 根据您的HTML更改此值
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment, // 将回调添加到TinyMCE
  tinycomments_reply: reply_comment,
  tinycomments_edit_comment: edit_comment,
  tinycomments_delete: delete_comment_thread,
  tinycomments_delete_all: delete_all_comment_threads,
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment,
  tinycomments_fetch: fetch_comments // 可选回调
});

tinycomments_reply

Comments插件使用tinycomments_reply函数回复评论。

tinycomments_reply函数将评论保存为对现有对话的回复,并在成功后通过done回调返回。不可恢复的错误通过调用fail回调而不是done来告知TinyMCE。

tinycomments_reply函数的第一个参数是一个请求(req)对象,具有以下字段:

  • conversationUid: 回复目标对话的uid。

  • content: 要创建的评论内容。

  • createdAt: 评论创建的日期(ISO 8601日期字符串)格式。

done回调应接受以下对象:

{
  commentUid: string, // 新的评论 uid
  author: string, // 当前作者的 id
  authorName: string // 当前作者的姓名
}
例如:
const reply_comment = (ref, done, fail) => {
  const { conversationUid, content, createdAt } = ref;

  fetch(`https://api.example/conversations/${conversationUid}`, {
    method: 'POST',
    body: JSON.stringify({ content: content, createdAt: createdAt }),
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
  })
    .then((response) => {
      if (!response.ok) {
        throw new Error('Failed to reply to comment');
      }
      return response.json();
    })
    .then((ref2) => {
      const commentUid = ref2.commentUid;
      done({
        commentUid: commentUid,
        author: currentUser.id,
        authorName: currentUser.fullName
      });
    })
    .catch((e) => {
      fail(e);
    });
}

tinymce.init({
  selector: 'textarea', // 根据 HTML 更改此值
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment,
  tinycomments_reply: reply_comment, // 将回调添加到 TinyMCE
  tinycomments_edit_comment: edit_comment,
  tinycomments_delete: delete_comment_thread,
  tinycomments_delete_all: delete_all_comment_threads,
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment,
  tinycomments_fetch: fetch_comments // 可选回调
});

tinycomments_edit_comment

Comments 插件使用 tinycomments_edit_comment 函数来编辑评论。

tinycomments_edit_comment 函数允许更新或更改现有评论,并在成功时通过 done 回调返回。不可恢复的错误则通过调用 fail 回调通知 TinyMCE。

tinycomments_edit_comment 函数的第一个参数是一个请求 (req) 对象,具有以下字段:

  • conversationUid:回复的目标对话的 uid。

  • commentUid:要编辑的评论的 uid(如果是编辑对话中的第一条评论,则可以与 conversationUid 相同)。

  • content:要创建的评论的内容。

  • modifiedAt:评论被修改的日期。

done 回调应接受以下对象:

{
  canEdit: boolean, // 编辑是否成功
  reason: string? // 解释为何不允许编辑的可选字符串(如果 canEdit 为 false)
}
例如:
const edit_comment = (ref, done, fail) => {
  const { conversationUid, commentUid, content, modifiedAt } = ref;

  fetch(
    `https://api.example/conversations/${conversationUid}/${commentUid}`,
    {
      method: 'PUT',
      body: JSON.stringify({ content: content, modifiedAt: modifiedAt }),
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
    }
  )
    .then((response) => {
      if (!response.ok) {
        throw new Error('Failed to edit comment');
      }
      return response.json();
    })
    .then((ref2) => {
      let canEdit = ref2.canEdit;
      done({ canEdit: canEdit });
    })
    .catch((e) => {
      fail(e);
    });
}

tinymce.init({
  selector: 'textarea', // 根据 HTML 更改此值
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment,
  tinycomments_reply: reply_comment,
  tinycomments_edit_comment: edit_comment, // 将回调添加到 TinyMCE
  tinycomments_delete: delete_comment_thread,
  tinycomments_delete_all: delete_all_comment_threads,
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment,
  tinycomments_fetch: fetch_comments // 可选回调
});

tinycomments_delete_comment

tinycomments_delete_comment 函数应使用 done 回调异步返回一个标志,指示是否已删除评论或评论线程。不可恢复的错误则通过调用 fail 回调通知 TinyMCE。

tinycomments_delete_comment 函数的第一个参数是一个请求 (req) 对象,具有以下字段:

  • conversationUid:回复的目标对话的 uid。

  • commentUid:要删除的评论的 uid(不能与 conversationUid 相同)。

done 回调应接受以下对象:

{
  canDelete: boolean, // 单个评论是否可以删除
  reason: string? // 解释为何不允许删除的可选原因(如果 canDelete 为 false)
}
由于权限或业务规则导致删除失败时,会通过“false”指示,而意外错误应使用“fail”回调指示。
例如:
const delete_comment = (ref, done, fail) => {
  const { conversationUid, commentUid } = ref;

  fetch(
    `https://api.example/conversations/${conversationUid}/${commentUid}`,
    {
      method: 'DELETE',
    }
  ).then((response) => {
    if (response.ok) {
      done({ canDelete: true });
    } else if (response.status === 403) {
      done({ canDelete: false });
    } else {
      fail(new Error('Something has gone wrong...'));
    }
  });
}

tinymce.init({
  selector: 'textarea', // 根据你的HTML更改此值
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment,
  tinycomments_reply: reply_comment,
  tinycomments_edit_comment: edit_comment,
  tinycomments_delete: delete_comment_thread, // 将回调添加到TinyMCE
  tinycomments_delete_all: delete_all_comment_threads,
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment,
  tinycomments_fetch: fetch_comments // 可选回调
});

tinycomments_delete

tinycomments_delete函数应异步返回一个标志,指示是否使用done回调删除了评论线程。无法恢复的错误应通过调用fail回调传达给TinyMCE。

tinycomments_delete函数的第一个参数是一个(req)对象,该对象包含以下键值对:

  • conversationUid:回复的目标对话的uid。

done回调应接受以下对象:

{
  canDelete: boolean // 对话是否可以删除
  reason: string? // 可选字符串,解释为什么不允许删除(如果canDelete为false)
}
由于权限或业务规则导致删除失败时,会通过“false”指示,而意外错误应使用“fail”回调指示。

例如:

const delete_comment_thread = (ref, done, fail) => {
  const conversationUid = ref.conversationUid;
  fetch(`https://api.example/conversations/${conversationUid}`, {
    method: 'DELETE',
  }).then((response) => {
    if (response.ok) {
      done({ canDelete: true });
    } else if (response.status === 403) {
      done({ canDelete: false });
    } else {
      fail(new Error('Something has gone wrong...'));
    }
  });
}

tinymce.init({
  selector: '#editor',
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment,
  tinycomments_reply: reply_comment,
  tinycomments_edit_comment: edit_comment,
  tinycomments_delete: delete_comment_thread, // 将回调添加到TinyMCE
  tinycomments_delete_all: delete_all_comment_threads,
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment,
  tinycomments_fetch: fetch_comments // 可选回调
});

tinycomments_delete_all

tinycomments_delete_all函数应异步返回一个标志,指示是否使用done回调删除了所有评论线程。无法恢复的错误应通过调用fail回调传达给TinyMCE。

tinycomments_delete_all函数的第一个参数是一个没有字段的请求(req)对象。

done回调应接受以下对象:

{
  canDelete: boolean, // 所有对话是否可以删除
  reason: string? // 可选字符串,解释为什么不允许删除所有(如果canDelete为false)
}
由于权限或业务规则导致删除失败时,应通过canDelete: false指示,而意外错误应使用fail回调指示。
例如:
const delete_all_comment_threads = (_req, done, fail) => {
  fetch('https://api.example/conversations', {
    method: 'DELETE',
  }).then((response) => {
    if (response.ok) {
      done({ canDelete: true });
    } else if (response.status === 403) {
      done({ canDelete: false });
    } else {
      fail(new Error('Something has gone wrong...'));
    }
  });
}

tinymce.init({
  selector: 'textarea', // 根据你的HTML更改此值
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment,
  tinycomments_reply: reply_comment,
  tinycomments_edit_comment: edit_comment,
  tinycomments_delete: delete_comment_thread,
  tinycomments_delete_all: delete_all_comment_threads, // 将回调添加到TinyMCE
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment,
  tinycomments_fetch: fetch_comments // 可选回调
});

tinycomments_lookup

Comments插件使用tinycomments_lookup函数通过对话的唯一ID检索一个现有的对话。

必须为tinycomments_lookup函数考虑显示名称配置:

显示名称

Comments插件使用一个简单的字符串作为显示名称。对于lookup函数,Comments期望每个评论都包含作者的显示名称,而不是用户ID,因为Comments不知道用户的身份。lookup函数应考虑这一点,并将用户标识符解析为适当的显示名称。

应通过 done 回调返回的传统对话对象结构如下:

tinycomments_lookup 函数的第一个参数是(req)对象,其中包含以下键值对:

conversationUid

回复目标对话的 uid。

done 回调应接受以下对象:

{
 conversation: {
   uid: string, // 对话的 uid,
   comments: [
    {
      author: string, // 第一条评论的作者
      authorName: string, // 可选 - 用于代替作者的显示名称。如果未指定,默认使用 `author`
      authorAvatar: string, // 可选 - 作者头像的 URL。如果未提供,将自动生成头像
      createdAt: date, // 第一条评论的创建时间
      content: string, // 第一条评论的内容
      modifiedAt: date, // 第一条评论的创建时间/最后更新时间
      uid: string // 对话中第一条评论的 uid
    },
    {
      author: string, // 第二条评论的作者
      authorName: string, // 可选 - 用于代替作者的显示名称。如果未指定,默认使用 `author`
      authorAvatar: string, // 可选 - 作者头像的 URL。如果未提供,将自动生成头像
      createdAt: date, // 第二条评论的创建时间
      content: string, // 第二条评论的内容
      modifiedAt: date, // 第二条评论的创建时间/最后更新时间
      uid: string // 对话中第二条评论的 uid
    }
  ]
 }
}

日期应使用 ISO 8601 格式。可以在 JavaScript 中使用 new Date().toISOString() 生成此格式。

作者头像功能仅在 TinyMCE 6.1 或更高版本中可用,如果提供:

  • 将缩放为直径 36px 的圆形;并且

  • 可以是任何能够包装在 <img> 元素中的文件类型。

例如:
const lookup_comment = ({ conversationUid }, done, fail) => {
  const lookup = async () => {
    const convResp = await fetch(
      `https://api.example/conversations${conversationUid}`
    );
    if (!convResp.ok) {
      throw new Error('Failed to get conversation');
    }
    const comments = await convResp.json();
    const usersResp = await fetch('https://api.example/users/');
    if (!usersResp.ok) {
      throw new Error('Failed to get users');
    }
    const { users } = await usersResp.json();
    const getUser = (userId) => users.find((u) => u.id === userId);
    return {
      conversation: {
        uid: conversationUid,
        comments: comments.map((comment) => {
          const user = getUser(comment.author);
          return {
            ...comment,
            content: comment.content,
            authorName: user?.displayName,
            authorAvatar: user?.avatarUrl
          };
        })
      }
    };
  };

  lookup()
    .then((data) => {
      console.log(`Lookup success ${conversationUid}`, data);
      done(data);
    })
    .catch((err) => {
      console.error(`Lookup failure ${conversationUid}`, err);
      fail(err);
    });
};

tinymce.init({
  selector: '#editor',
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment,
  tinycomments_reply: reply_comment,
  tinycomments_edit_comment: edit_comment,
  tinycomments_delete: delete_comment_thread,
  tinycomments_delete_all: delete_all_comment_threads,
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment, // Add the callback to TinyMCE
  tinycomments_fetch: fetch_comments // Optional callback
});

可选回调函数

tinycomments_resolve

此选项在对话的第一条评论的下拉菜单中添加一个“Resolve Conversation”项。

tinycomments_resolve 函数应异步返回一个标志,指示是否使用 done 回调解决了评论线程。无法恢复的错误通过调用 fail 回调传达给 TinyMCE。

tinycomments_resolve 函数的第一个参数是(req)对象,其中包含以下键值对:

  • conversationUid:目标对话的 uid

done 回调应接受以下对象:

{
  canResolve: boolean // 对话是否可以解决
  reason?: string // 可选字符串,解释为什么不允许解决(如果 canResolve 为 false)
}
由于权限或业务规则无法解析的情况,应通过 canResolve: false 指示,而意外错误应使用 fail 回调指示。
例如:
const resolve_comment_thread = (ref, done, fail) => {
  const conversationUid = ref.conversationUid;
  fetch(`https://api.example/conversations/${conversationUid}`, {
    method: 'PUT',
  }).then((response) => {
    if (response.ok) {
      done({ canResolve: true });
    } else if (response.status === 403) {
      done({ canResolve: false });
    } else {
      fail(new Error('Something has gone wrong...'));
    }
  });
}

tinymce.init({
  selector: 'textarea', // 根据 HTML 更改此值
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment,
  tinycomments_reply: reply_comment,
  tinycomments_edit_comment: edit_comment,
  tinycomments_resolve: resolve_comment_thread, // 将回调添加到 TinyMCE
  tinycomments_delete: delete_comment_thread,
  tinycomments_delete_all: delete_all_comment_threads,
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment,
  tinycomments_fetch: fetch_comments
});

tinycomments_fetch

Comments 插件使用 tinycomments_fetch 回调函数根据其唯一标识符检索多个会话。

应通过 done 回调返回的常规会话对象结构如下:

tinycomments_fetch 函数的第一个参数是一个 (req) 对象,其中包含以下键值对:

  • conversationUids:一个字符串数组,表示要获取的会话的唯一标识符。

done 回调应接受以下对象:

{
  conversations: {
    [conversationUid: string]: {
      uid: string,
      comments: [
        {
          uid: string,
          author: string,
          authorName?: string,
          authorAvatar?: string,
          content: string,
          createdAt: string, // ISO 8601 日期字符串
          modifiedAt: string // ISO 8601 日期字符串
        },
        // ... 更多评论
      ]
    },
    // ... 更多会话
  }
}
例如:
const tinycomments_fetch = (conversationUids, done, fail) => {
  const requests = conversationUids.map((uid) => fetch(`https://api.example/conversations/${uid}`, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      }})
      .then((response) => response.json())
      .then((data) => ({
        [uid]: {
          uid: uid,
          comments: data
        }
      }))
    );

  Promise.all(requests)
    .then((data) => {
      console.log('data', data);
      const conversations = data.reduce((conv, d) => ({
          ...conv,
          ...d
        })
      , {});
      console.log(`Fetch success ${conversationUids}`, conversations);
      done({ conversations });
    })
    .catch((err) => {
      console.error(`Fetch failure ${conversationUids}`, err);
      fail('Fetching conversations failed');
    });

tinymce.init({
  selector: '#editor',
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment,
  tinycomments_reply: reply_comment,
  tinycomments_edit_comment: edit_comment,
  tinycomments_delete: delete_comment_thread,
  tinycomments_delete_all: delete_all_comment_threads,
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment,
  tinycomments_fetch: fetch_comments // 将回调添加到 TinyMCE
});

user_id

此选项设置编辑器中当前用户的唯一标识符。它用于 UserLookup API。

类型:String

默认值:'Anonymous'

例如:使用 user_id 选项
tinymce.init({
  selector: 'textarea',  // 根据 HTML 更改此值
  user_id: 'james-wilson' // 用唯一字符串替换,以标识用户
});

fetch_users

一个必需的回调函数,用于获取用户数据。此函数以用户 ID 数组调用,应返回一个 Promise,该 Promise 解析为用户对象数组。UserLookup API 使用此回调。如果返回的数组不包含所有请求的用户 ID,则缺失用户的 Promise 将被拒绝,并显示 "User {id} not found" 错误。

类型:Function

参数:- ids (Array<string>):要获取的用户 ID 数组。

返回值:- Promise<Array<Object>>:解析为用户对象数组的 Promise。

例如:使用 fetch_users 选项
const API_URL = 'https://demouserdirectory.tiny.cloud/v1/users';

tinymce.init({
  selector: 'textarea',
  user_id: 'james-wilson',
  fetch_users: (userIds) => Promise.all(userIds
    .map((userId) =>
      fetch(`${API_URL}/${userId}`)
        .then((response) => response.json())
        .catch(() => ({ id: userId })))),
});
示例:返回带有验证的用户数组
const API_URL = 'https://demouserdirectory.tiny.cloud/v1/users';

tinymce.init({
  selector: 'textarea',
  user_id: 'james-wilson',
  fetch_users: async (userIds) => {
    const users = await Promise.all(userIds
        .map((userId) =>
        fetch(`${API_URL}/${userId}`)
            .then((response) => response.json())
            .catch(() => ({ id: userId }))));

    return userIds.map(
      (userId) =>
        users.find((user) => user.id === userId)
        || ({ id: userId }) // 即使服务器未返回该用户,仍返回一个有效的用户
    );
  },
});

tinycomments_fetch_author_info

此选项在 TinyMCE 8 中已被弃用,可能会在 TinyMCE 未来的主版本中被移除。请使用 user_id 和 fetch_users 的组合来代替。 | 用户查找 API

Comments 插件使用 tinycomments_fetch_author_info 回调函数来提供在编辑器中显示的评论的作者详细信息。

每当渲染一条评论时,此函数都会被调用,它允许编辑器根据当前用户显示作者的姓名和头像。

tinycomments_fetch_author_info 函数会接收一个 done 回调参数。

该函数必须调用 done 并传入一个包含以下属性的对象:

  • author (string):用户的唯一标识符。

  • authorName (string):要显示的用户全名。(可选)

  • authorAvatar (string):指向用户头像图片的 URL。(可选)

tinycomments_fetch_author_info 函数示例
const tinycomments_fetch_author_info = (done) => {
  setTimeout(() => done({
    author: currentUser.id, // 映射到评论对象中的 `author` 字段
    authorName: currentUser.fullName, // 映射到评论对象中的 `authorName` 字段。(可选)
    authorAvatar: currentUser.image, // 映射到评论对象中的 `authorAvatar` 字段。(可选)
  }), fakeDelay);
};
tinycomments_fetch_author_info 函数与自定义作者的示例
const currentUser = {
  id: 'johnsmith',
  name: 'John Smith',
  fullName: 'John Smith',
  description: 'Company Founder',
  image: '{{imagesdir}}/users/johnsmith.png'
};

const tinycomments_fetch_author_info = (done) => {
  fetch('https://example.api.com/currentUser')
    .then(res => res.json())
    .then(currentUser => {
      done({
        author: currentUser.id, // 映射到评论对象中的 `author` 字段
        authorName: currentUser.fullName, // 映射到评论对象中的 `authorName` 字段。(可选)
        authorAvatar: currentUser.image, // 映射到评论对象中的 `authorAvatar` 字段。(可选)
      });
    });
};

tinymce.init({
  selector: '#editor',
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create: create_comment,
  tinycomments_reply: reply_comment,
  tinycomments_edit_comment: edit_comment,
  tinycomments_delete: delete_comment_thread,
  tinycomments_delete_all: delete_all_comment_threads,
  tinycomments_delete_comment: delete_comment,
  tinycomments_lookup: lookup_comment,
  tinycomments_fetch: fetch_comments, // 可选回调
  tinycomments_fetch_author_info: fetch_comments
});

tinycomments_always_show_highlights

可选:Comments 插件提供 tinycomments_always_show_highlights 选项,用于在 Comments 侧边栏关闭时保持对评论内容的高亮显示。默认情况下,TinyMCE 仅在侧边栏打开时高亮显示评论内容,一旦侧边栏关闭,就会移除编辑器内容上的高亮显示。

当此选项设置为 true 时,无论侧边栏是打开还是关闭,TinyMCE 都会高亮显示评论内容,并且选中高亮显示的内容会重新打开已关闭的 Comments 侧边栏。

类型: Boolean

默认值: false

可能的值: true,false

示例:使用 tinycomments_always_show_highlights

const currentAuthor = 'embedded_journalist';

tinymce.init({
  selector: 'textarea',  // 根据您的 html 更改此值
  plugins: 'tinycomments',
  toolbar: 'addcomment showcomments',
  tinycomments_mode: 'embedded',
  user_id: currentAuthor,
  tinycomments_always_show_highlights: true
});

在编辑器加载时显示侧边栏

可以使用 sidebar_show 选项在编辑器加载时显示 Comments 侧边栏。

例如:
tinymce.init({
  selector: 'textarea',  // 根据您的 html 更改此值
  plugins: 'tinycomments',
  tinycomments_mode: 'callback',
  tinycomments_create,
  tinycomments_reply,
  tinycomments_edit_comment,
  tinycomments_delete,
  tinycomments_delete_all,
  tinycomments_delete_comment,
  tinycomments_lookup,
  tinycomments_fetch,
  sidebar_show: 'showcomments'
});

配置评论文本和块的 CSS 属性

高亮样式现在是整体内容皮肤的一部分,并且通过自定义皮肤来更改。

TinyMCE 开源项目 oxide(默认皮肤)定义了用于更改批注颜色的变量。

请参考 文档以了解如何使用此代码库构建皮肤。

有关配置 TinyMCE 格式的更多信息,请参阅 formats 部分。