projectdoc Toolbox

Enforces comments in a specific format when documents are edited.

Tags
Identifier
de.smartics.userscripts.confluence.force-comment
Type
Repository
Since
1.0

The script checks in the Confluence page editor whether or not the comment adheres to the required format. If not the page cannot be saved.

If the change is either a feature or a fix, then the watchers of the page will be notified.

Code

The code of the script for reference.

force-comment.js
/*
 * Copyright 2019-2024 Kronseder & Reiner GmbH, smartics
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
"use strict";

AJS.toInit(function () {
  const logToConsole = false;

  const commentPrefixes = ["change: ", "feat: ", "fix: ", "refactor: ", "style: ", "chore: "];
  const notificationRequiredIndex = 2;

  if (logToConsole) {
    AJS.log("[force-comment] Force comment with: " + commentPrefixes);
  }

  const isValidComment = function (comment) {
    if (comment) {
      for (let i = 0; i < commentPrefixes.length; i++) {
        const commentPrefix = commentPrefixes[i];
        if (comment.startsWith(commentPrefix) &&
          comment.length > commentPrefix.length) {
          return true;
        }
      }
    }
    return false;
  };

  const requiresNotification = function (comment) {
    if (comment) {
      for (let i = 0; i <= notificationRequiredIndex; i++) {
        const commentPrefix = commentPrefixes[i];
        if (comment.startsWith(commentPrefix)) {
          return true;
        }
      }
    }
    return false;
  };

  const updateNotification = function (comment) {
    const $notify = AJS.$('#notifyWatchers');
    if (requiresNotification(comment)) {
      $notify.prop('checked', true);
    } else {
      $notify.prop('checked', false);
    }
  };

  const monitorPublishButtonStatus = function () {
    const comment = AJS.$('#versionComment').val();
    if (logToConsole) AJS.log("[force-comment] Checking comment: " + comment);
    const currentStatus = AJS.$('#rte-button-publish').prop('disabled');
    if (!isValidComment(comment)) {
      if (logToConsole) AJS.log("[force-comment] Comment '" + comment + "' is not valid, therefore disabling publish button.");
      if (!currentStatus) {
        AJS.$('#rte-button-publish').prop('disabled', true);
        updateNotification(comment);
      }
    } else {
      if (logToConsole) AJS.log("[force-comment] Comment '" + comment + "' is valid, therefore enabling publish button.");
      if (currentStatus) {
        AJS.$('#rte-button-publish').prop('disabled', false);
        updateNotification(comment);
      }
    }
  };

  const createHelpDialog = function ($element) {
    if (AJS.$("#userscripts-commitMessages-helpDialog-show-button").length) {
      return;
    }
    const button = AJS.$("<button id=\"userscripts-commitMessages-helpDialog-show-button\" class=\"aui-button toolbar-item\" style=\"margin-right: 10px;\"><span class=\"aui-icon aui-icon-small aui-iconfont-question-filled\">Help on Commit Message Format</span></button>\n");
    $element.before(button);

    AJS.$("#userscripts-commitMessages-helpDialog-show-button").on('click', function (e) {
      e.preventDefault();

      const dialog = AJS.$("<section\n" +
        "    id=\"userscripts-commitMessages-helpDialog\"\n" +
        "    class=\"aui-dialog2 aui-dialog2-medium aui-layer\"\n" +
        "    role=\"dialog\"\n" +
        "    tabindex=\"-1\"\n" +
        "    data-aui-modal=\"false\"" +
        "    data-aui-remove-on-hide=\"true\"" +
        "    aria-labelledby=\"userscripts-commitMessages-helpDialog-show-button--heading\"\n" +
        "    hidden\n" +
        ">\n" +
        "    <header class=\"aui-dialog2-header\">\n" +
        "        <h1 class=\"aui-dialog2-header-main\" id=\"userscripts-commitMessages-helpDialog-show-button--heading\">Help: Commit message format</h1>\n" +
        "    </header>\n" +
        "    <div class=\"aui-dialog2-content\">\n" +
        "        <p>Use the following format for your commit messages.</p>\n" +
        "<table class=\"aui\">\n" +
        "    <thead>\n" +
        "        <tr>\n" +
        "            <th id=\"type\">Type</th>\n" +
        "            <th id=\"description\">Description</th>\n" +
        "            <th id=\"notify\" style=\"text-align:center;\">Notify</th>\n" +
        "            <th id=\"example\">Example</th>\n" +
        "        </tr>\n" +
        "    </thead>\n" +
        "    <tbody>\n" +
        "        <tr>\n" +
        "            <td headers=\"type\" style=\"font-weight: bold;\">Change</td>\n" +
        "            <td headers=\"description\">Alters or removes an existing feature.</td>\n" +
        "            <td headers=\"notify\" style=\"text-align:center;\">\n<span class=\"aui-icon aui-icon-small aui-iconfont-approve\">Checks the 'Notify watchers' checkbox</span></td>\n" +
        "            <td headers=\"example\">\n" +
        "                <code>change: Replace response code format.</code>\n" +
        "            </td>\n" +
        "        </tr>\n" +
        "        <tr>\n" +
        "            <td headers=\"type\" style=\"font-weight: bold;\">Feature</td>\n" +
        "            <td headers=\"description\">Adds essential new information.</td>\n" +
        "            <td headers=\"notify\" style=\"text-align:center;\">\n<span class=\"aui-icon aui-icon-small aui-iconfont-approve\">Checks the 'Notify watchers' checkbox</span></td>\n" +
        "            <td headers=\"example\">\n" +
        "                <code>feat: Add section on error handling.</code>\n" +
        "            </td>\n" +
        "        </tr>\n" +
        "        <tr>\n" +
        "            <td headers=\"type\" style=\"font-weight: bold;\">Fix</td>\n" +
        "            <td headers=\"description\">Fixes an issue or false information.</td>\n" +
        "            <td headers=\"notify\" style=\"text-align:center;\">\n<span class=\"aui-icon aui-icon-small aui-iconfont-approve\">Checks the 'Notify watchers' checkbox</span></td>\n" +
        "            <td headers=\"example\">\n" +
        "                <code>fix: Clarify section on user administration which is misleading.</code>\n" +
        "            </td>\n" +
        "        </tr>\n" +
        "        <tr>\n" +
        "            <td headers=\"type\" style=\"font-weight: bold;\">Refactor</td>\n" +
        "            <td headers=\"description\">Reorganize sections or rename elements.</td>\n" +
        "            <td headers=\"notify\" style=\"text-align:center;\">\n<span class=\"aui-icon aui-icon-small aui-iconfont-cross-circle\">Unchecks the 'Notify watchers' checkbox</span></td>\n" +
        "            <td headers=\"example\">\n" +
        "                <code>refactor: Split administration section into three subsections.</code>\n" +
        "            </td>\n" +
        "        </tr>\n" +
        "        <tr>\n" +
        "            <td headers=\"type\" style=\"font-weight: bold;\">Style</td>\n" +
        "            <td headers=\"description\">Change the wording, remove typos, or fix grammar.</td>\n" +
        "            <td headers=\"notify\" style=\"text-align:center;\">\n<span class=\"aui-icon aui-icon-small aui-iconfont-cross-circle\">Unchecks the 'Notify watchers' checkbox</span></td>\n" +
        "            <td headers=\"example\">\n" +
        "                <code>style: Replace with active voice to address the reader.</code>\n" +
        "            </td>\n" +
        "        </tr>\n" +
        "        <tr>\n" +
        "            <td headers=\"type\" style=\"font-weight: bold;\">Chore</td>\n" +
        "            <td headers=\"description\">Necessary adjustments without regards to content.</td>\n" +
        "            <td headers=\"notify\" style=\"text-align:center;\">\n<span class=\"aui-icon aui-icon-small aui-iconfont-cross-circle\">Unchecks the 'Notify watchers' checkbox</span></td>\n" +
        "            <td headers=\"example\">\n" +
        "                <code>chore: Update icon to higher resolution.</code>\n" +
        "            </td>\n" +
        "        </tr>\n" +
        "    </tbody>\n" +
        "</table>" +
        "    </div>\n" +
        "    <footer class=\"aui-dialog2-footer\">\n" +
        "        <div class=\"aui-dialog2-footer-actions\">\n" +
        "            <button id=\"userscripts-commitMessages-helpDialog-submit-button\" class=\"aui-button aui-button-primary\">Okay</button>\n" +
        "        </div>\n" +
        "    </footer>\n" +
        "</section>");

      $element.before(dialog);

      AJS.$("#userscripts-commitMessages-helpDialog-submit-button").on('click', function (e) {
        e.preventDefault();
        AJS.dialog2("#userscripts-commitMessages-helpDialog").hide();
      });

      AJS.dialog2("#userscripts-commitMessages-helpDialog").show();
    });
  };

  AJS.$(function () {
    if (logToConsole) {
      AJS.log("[force-comment] Analysing page elements: " + commentPrefixes);
    }
    const $versionComment = AJS.$('#versionComment');

    if ($versionComment.length) {
      createHelpDialog($versionComment);

      const $publishButton = AJS.$('#rte-button-publish');
      const $notify = AJS.$('#notifyWatchers');

      if ($publishButton.length && $notify.length) {
        $versionComment.bind("input change", function () {
          const comment = $versionComment.val();
          if (isValidComment(comment)) {
            $publishButton.prop('disabled', false);
            updateNotification(comment);
          } else {
            $publishButton.prop('disabled', true);
          }
        });

        USERSCRIPT4C_SYNC.monitorPage(monitorPublishButtonStatus, AJS.$("#savebar-container"));

        const comment = $versionComment.val();
        if (!isValidComment(comment)) {
          $publishButton.prop('disabled', true);
        }
      }
    }
  });
});

Details

Describing the use case for this script. First the use case for the author then for the userscripts administrator.

Commenting Changes

When opening a page where this script enforces the format of a comment, the Save button will be deactivated per default.

Screenshot showing the deactivated Save button.

Users who do not know how to work with comments, may click the Help button ().

Screenshot showing the Help dialog for Commit Messages.

Entering a valid comment will activate the Save button.

Screenshot showing a valid comment and the activated Save button.

If the comment does not meet the constraints, then the page cannot be saved.

Screenshot showing an invalid comment and therefore the inactive Save button.

If the comment is indicating a new feature, then the checkbox for Notify watchers will be checked.

Screenshot showing the checked box for Notify watchers.

Configuration of Userscript

Typically there is no need to enforce specific comments for each and every change for a page on a Confluence server. Typically versioned documents are located in a specific space. To activate the script for spaces with versioned documents, the userscripts administrator may define a space category, like versioned, to be set.

Screenshot of the REST API Browser with the configuration for this userscript.

Configure the space category via the Space Tools.

Screenshot with Space Details on Space Tools.

Once the categories contain a category 'versioned', as specified in the activation record via activation-space-categories, the space will enforce the comment on every document.

Related Scripts

NameShort Description
Create with Template
Removes the default create-page button and renames the create with template.
Shortcuts for Focus
Sample script to register actions with a shortcut in Confluence.