Discourse 建议的编辑

:writing_hand: 允许社区成员建议对帖子进行编辑,使审阅者对接受哪些更改拥有精细的控制权——而无需授予完全的编辑权限。

:warning: 此插件目前处于实验阶段,可能会有很大变动,尚不适用于生产环境。

:link: GitHub - discourse/discourse-suggested-edits: EXPERIMENTAL suggested edits plugin · GitHub

安装

按照标准插件安装指南,使用以下仓库 URL:

https://github.com/discourse/discourse-suggested-edits.git

为什么要使用建议编辑?

许多社区希望成员帮助保持内容准确和最新,但授予每个人编辑权限并不总是可行的。建议编辑弥合了这一差距——成员可以提议改进帖子,受信任的审阅者决定采纳哪些内容。可以将其视为将类似维基百科的贡献模式带到您的 Discourse 社区。

这对于以下情况特别有用:

  • 知识库和文档类别,其中准确性很重要,并且多双眼睛有帮助
  • 新成员较多的社区,他们有好的贡献但尚未获得完全的编辑信任
  • 协作内容,如常见问题解答、指南或社区维护的参考资料
  • 自动化编辑,有时人工智能系统可能会建议修复拼写错误和语调问题,并且需要有人工参与批准

工作原理

建议编辑

在配置的建议组中的成员将在符合条件的帖子上有“建议编辑”按钮。点击该按钮会打开预先填充了帖子内容的编辑器。他们进行更改,可以选择添加原因,然后提交。

image

审阅建议

审阅者会在有待处理建议的帖子旁看到一个计数徽章。点击“审阅”会打开一个模态框,将建议分解为单个更改——每个更改都显示为带有周围上下文的高亮差异。

审阅者可以:

  • 独立接受或拒绝每项更改——无需全盘接受或拒绝
  • 在应用前编辑建议的文本——在保留意图的同时微调措辞
  • 在行内和并排差异视图之间切换
  • 在多个建议之间导航(如果有多个待处理)

应用更改

当审阅者点击“应用接受的更改”时,所选的更改将作为归属于建议者的修订应用于帖子,编辑原因会注明批准人。建议者和任何其他受影响的用户都会收到通知。

停滞处理

如果原始帖子在创建建议后被编辑,该建议将自动标记为停滞(stale)且无法应用。这可以防止冲突,并确保建议始终基于当前内容。建议者会收到通知,以便在需要时重新提交。

配置

管理 > 设置下启用插件并配置访问权限,搜索“suggested edits”:

设置 描述
suggested_edits_enabled 插件的主开关
suggested_edits_suggest_groups 可以建议编辑的组成员所属的组
suggested_edits_review_groups 可以审阅和应用建议的组成员所属的组。帖子作者始终可以审阅其自己帖子的建议。
suggested_edits_included_categories 启用了建议编辑的类别
suggested_edits_included_tags 在其中启用了建议编辑的主题的标签
suggested_edits_max_creates_per_minute 创建建议的速率限制(默认:5)
suggested_edits_max_revisions_per_minute 审阅建议的速率限制(默认:10)

典型设置

  1. 启用插件
  2. 建议组设置为应该能够提议编辑的信任级别或组(例如 trust_level_1
  3. 审阅组设置为您的版主或策展人(例如 staff
  4. 选择您希望启用此功能的类别标签——您不必在所有地方都启用它

:bulb: 帖子作者始终可以审阅其自己帖子的建议,无论审阅组设置如何。

范围和限制

  • 仅限首帖——建议编辑目前仅适用于主题的首帖(OP),不适用于回复
  • 每个帖子每个用户只能有一个待处理的建议——成员必须等待他们当前的建议得到解决,才能在同一帖子中提交另一个建议
  • 建议基于文本——差异是根据帖子的原始 Markdown 内容计算的

搜索

审阅者可以使用搜索过滤器 with:suggested-edits 来查找论坛中所有有待处理建议的主题。

17 个赞

我认为我没有收到通知。

3 个赞

嗯……当有人建议编辑帖子时,会通知该帖子的原作者(OP)吗?我查看了代码,但没有发现任何相关迹象。

2 个赞

不行,尚未实现,将会添加

2 个赞

您好,好主意!在选择分类时,能否只选择一级分类,同时自动选中其子分类?谢谢。

1 个赞

供参考,此插件抛出错误:

plugin-api.gjs:234 [PLUGIN discourse-suggested-edits] 尝试修改 "service:composer",但它已在引导过程的更早阶段初始化(例如通过 lookup())。请移除该 lookup,或将 modifyClass 调用移至引导过程的更早阶段以使更改生效。https://meta.discourse.org/t/262064
_resolveClass @ plugin-api.gjs:234

看起来初始化器在调用 api.modifyClass("service:composer", {...}) 之前调用了 api.customizeComposerText()。我认为代码需要重新排序,使 api.modifyClassapi.customizeComposerText 之前运行:

或许应该这样?
  // 先修改 composer 类

  api.modifyClass("service:composer", {
    pluginId: "discourse-suggested-edits",

    async open(opts) {
      if (isSuggestedEditAction(opts.action) && this.model) {
        this.model.set("disableDrafts", true);
        this.skipAutoSave = true;
        this.close();
        this.skipAutoSave = false;
      }
      setSuggestEditActive(
        isSuggestedEditAction(opts.action),
        opts.metaData?.originalRaw
      );
      await this._super(opts);
      if (isSuggestedEditModel(this.model)) {
        this.model.setProperties({
          disableDrafts: true,
          draftStatus: null,
          draftConflictUser: null,
        });
      }
    },

    cancelComposer(opts) {
      if (isSuggestedEditModel(this.model)) {
        setSuggestEditActive(false);
        this.skipAutoSave = true;
        this.close();
        this.appEvents.trigger("composer:cancelled");
        this.skipAutoSave = false;
        return Promise.resolve();
      }
      return this._super(opts);
    },

    destroyDraft() {
      if (isSuggestedEditModel(this.model)) {
        return Promise.resolve();
      }
      return this._super();
    },

    _saveDraft(showToast = false) {
      if (isSuggestedEditModel(this.model)) {
        return Promise.resolve();
      }

      return this._super(showToast);
    },

    save(force, options = {}) {
      if (isSuggestedEditModel(this.model)) {
        return this._saveSuggestedEdit();
      }
      return this._super(force, options);
    },

    async _saveSuggestedEdit() {
      const model = this.model;
      const meta = model.metaData || {};

      if (model.reply?.trim() === meta.originalRaw?.trim()) {
        this.toasts.error({
          data: {
            message: i18n("discourse_suggested_edits.composer.no_changes"),
          },
          duration: "short",
        });
        return;
      }

      try {
        if (meta.existingSuggestionId) {
          await updateSuggestedEdit(meta.existingSuggestionId, {
            raw: model.reply,
            reason: meta.reason,
          });
          this.toasts.success({
            data: {
              message: i18n("discourse_suggested_edits.toast.updated"),
            },
            duration: "short",
          });
        } else {
          const result = await createSuggestedEdit({
            postId: meta.postId,
            raw: model.reply,
            reason: meta.reason,
          });
          if (model.topic) {
            model.topic.set(
              "own_pending_suggested_edit_id",
              result.suggested_edit.id
            );
          }
          this.toasts.success({
            data: {
              message: i18n("discourse_suggested_edits.toast.created"),
            },
            duration: "short",
          });
        }

        setSuggestEditActive(false);
        this.close();
      } catch (e) {
        popupAjaxError(e);
      }
    },
  });

  // 在类被修改后再添加自定义

  api.customizeComposerText({
    actionTitle(model) {
      if (model.action === SUGGEST_EDIT_ACTION) {
        return i18n("discourse_suggested_edits.composer.action_title");
      }
    },
    saveLabel(model) {
      if (model.action === SUGGEST_EDIT_ACTION) {
        return "discourse_suggested_edits.composer.save_label";
      }
    },
  });
1 个赞

我们可能需要将其移至预初始化阶段……我会看看的,感谢尝试 @Lilly

1 个赞