Creates a continuation document on the current day in the user's journal based on the document the browser is showing. Support events, todos, and other documents that will be stored on a day document.

Repository

A journal keeps tracks of the events and todos of a team or a single team member. When work is not finished in one go or related work is a connected series of events. Todos may also have iterative character like jour fixes. The Continue with Bookmarklet allows a team member to continue a previous task or todo today by copying the relevant metadata.

Not only is a new event or todo created and initially filled, but also can the continued event or todo be marked as finished.

To use this bookmarklet besides the Bookmarklets Extension (and its prerequisites including the projectdoc Toolbox) the projectdoc Developer Diaries (including its prerequisites) need also be installed on a Confluence server.

Bookmarklet

The bookmarklet is ready to use. Simply drag and drop the bookmarklet button to the bookmarks in your browser.

Ready to use!

Error rendering macro 'projectdoc-bookmarklet-button-macro'

org/apache/commons/lang/StringUtils

Code

The code is available on the resource repository.

continue-with.js
    AJS.toInit(function () {
      // const sourceDocumentDoctype = AJS.$("div.projectdoc-document-element.properties").data("projectdoc-doctype");
      // if (["event", "todo", "report"].indexOf(sourceDocumentDoctype) < 0) {
      //   return;
      // }

      // const remoteUser = AJS.Meta.get('remote-user');
      const spaceKey = AJS.Meta.get('space-key');
      const pageId = AJS.Meta.get('page-id');
      const baseUrl = AJS.Meta.get('base-url');

      const labelTitle = "Continue with ...";
      const labelSubmit = "Submit";
      const labelCancel = "Cancel";

      const finishDocumentRequestCheckboxId = "finish-document-request";

      function appendElementWithClass(parent, tagName, classAttributeValue) {
        return appendElement(parent, tagName, "class", classAttributeValue);
      }

      function appendElement(parent, tagName, attributeName, attributeValue) {
        const element = document.createElement(tagName);
        parent.append(element);
        if (attributeName) {
          element.setAttribute(attributeName, attributeValue);
        }
        return element;
      }

      function appendDialog(i18n, currentSpaceKey, targetSpaceKey, location, currentDocument, currentRenderedValues) {
        const dialogHtml = document.createElement("section");
        dialogHtml.setAttribute("id", "projectdoc-continue-document-dialog");
        dialogHtml.setAttribute("class", "aui-dialog2 aui-dialog2-medium aui-layer");
        dialogHtml.setAttribute("role", "dialog");
        dialogHtml.setAttribute("aria-hidden", "true");

        const header = appendElementWithClass(dialogHtml, "header", "aui-dialog2-header");
        const h2 = appendElementWithClass(header, "h2", "aui-dialog2-header-main");
        h2.append(document.createTextNode(labelTitle));

        const content = appendElementWithClass(dialogHtml, "div", "aui-dialog2-content");
        const footer = appendElementWithClass(dialogHtml, "div", "aui-dialog2-footer");
        const footerDiv = appendElementWithClass(footer, "div", "aui-dialog2-footer-actions");
        const submitButton = appendElementWithClass(footerDiv, "button", "aui-button aui-button-primary");
        submitButton.setAttribute("id", "projectdoc-continue-document-dialog-submit");
        submitButton.append(document.createTextNode(labelSubmit));
        const cancelButton = appendElementWithClass(footerDiv, "button", "aui-button aui-button-link");
        cancelButton.setAttribute("id", "projectdoc-continue-document-dialog-cancel");
        cancelButton.append(document.createTextNode(labelCancel));

        const form = appendElementWithClass(content, "form", "aui");
        form.setAttribute("id", "projectdoc-continue-document-dialog-form");
        Object.keys(currentDocument).forEach(function (key) {
          if (key !== i18n.spaceKey && key !== i18n.title && key !== i18n.doctype && key !== i18n.iteration) {
            const value = currentDocument[key];
            if ((value && !value.startsWith("<ac:placeholder>")) || key === i18n.name) {
              const formGroup = appendElementWithClass(form, "div", "field-group");
              const groupLabel = appendElement(formGroup, "label", "for", key);
              groupLabel.append(document.createTextNode(key));
              const idPart = PDBMLS.replaceAll(key, " ", "_");

              if (key !== i18n.name && key !== i18n.shortDescription) {
                const checkbox = appendElementWithClass(formGroup, "input", "checkbox");
                checkbox.setAttribute("type", "checkbox");
                checkbox.setAttribute("id", "projectdoc-continue-document-dialog-checkbox-" + idPart);
                checkbox.setAttribute("name", key);
                checkbox.setAttribute("checked", "checked");
              }

              let groupInput;
              if (key === i18n.shortDescription) {
                groupInput = appendElementWithClass(formGroup, "textarea", "textarea full-width-field");
                groupInput.append(document.createTextNode(value));
              } else {
                if (key === i18n["object"]) {
                  groupInput = appendElement(formGroup, "div");
                  const htmlValue = currentRenderedValues[key];
                  groupInput.insertAdjacentHTML("afterbegin", htmlValue);
                } else {
                  groupInput = appendElementWithClass(formGroup, "input", "text full-width-field");
                  groupInput.setAttribute("value", value);
                }

                if (key !== i18n.name) {
                  if (key !== i18n["object"]) {
                    groupInput.setAttribute("readonly", "true");
                  }
                }
              }
              groupInput.setAttribute("id", "projectdoc-continue-document-dialog-field-" + idPart);
              groupInput.setAttribute("name", key);
            }
          }
        });

        const isFinishedOrLater = function (iteration) {
          return iteration === 'Finished' || iteration === 'Released' || iteration === 'Production' || iteration === 'Deprecated' || iteration === 'Removed';
        };

        if (!isFinishedOrLater(currentDocument[i18n.iteration])) {
          const formGroup = appendElementWithClass(form, "div", "field-group");
          const groupLabel = appendElement(formGroup, "label", "for", finishDocumentRequestCheckboxId);
          const inputName = "Finish current document?";
          groupLabel.append(document.createTextNode(inputName));
          const checkbox = appendElementWithClass(formGroup, "input", "checkbox");
          checkbox.setAttribute("type", "checkbox");
          checkbox.setAttribute("id", "projectdoc-continue-document-dialog-checkbox-" + finishDocumentRequestCheckboxId);
          checkbox.setAttribute("name", inputName);
          checkbox.setAttribute("checked", "checked");
        }

        AJS.$("body").append(dialogHtml);

        AJS.$("#projectdoc-continue-document-dialog-submit").on('click', function (e) {
          e.preventDefault();
          AJS.dialog2("#projectdoc-continue-document-dialog").hide();

          const sourceDocumentDoctype = AJS.$("div.projectdoc-document-element.properties").data("projectdoc-doctype");
          const targetDocumentDoctype = null !== sourceDocumentDoctype && sourceDocumentDoctype !== 'todo' ? sourceDocumentDoctype : 'event';
          const newDocument = PDBMLS.createDocument(i18n, targetDocumentDoctype);
          const continuationOfName = sourceDocumentDoctype !== 'report' ? i18n["continuationOf"] : i18n["projectdoc.doctype.report.last-report"];
          // "<ac:link><ri:page ri:space-key='" + currentDocument[i18n.spaceKey] + "' ri:content-title='" + currentDocument[i18n.title] + "' /></ac:link>";
          const continuationOfValue = AJS.$("<ac:link>").html(AJS.$("<ri:page>").attr("ri:space-key", currentDocument[i18n.spaceKey]).attr("ri:content-title", currentDocument[i18n.title])).prop('outerHTML');
          newDocument.addProperty(continuationOfName, continuationOfValue, null, "replace");

          copyPropertyFromInputField(i18n, i18n.name, newDocument, "replace", "hide");
          copyPropertyFromTextarea(i18n, i18n.shortDescription, newDocument, "replace");
          copyPropertyFromFieldCheckingToggle(i18n, i18n.subject, newDocument);
          copyPropertyFromFieldCheckingToggle(i18n, i18n.categories, newDocument);
          copyPropertyFromFieldCheckingToggle(i18n, i18n.tags, newDocument);
          copyPropertyFromFieldCheckingToggle(i18n, i18n['type'], newDocument);
          copyPropertyFromFieldCheckingToggle(i18n, i18n.participants, newDocument);
          const objectName = i18n["object"];
          const objectValue = currentDocument[objectName];
          if (objectValue && !objectValue.startsWith("<ac:placeholder>")) {
            copyStoragePropertyCheckingToggle(i18n, objectName, objectValue, newDocument, "replace");
          }
          copyPropertyFromFieldCheckingToggle(i18n, i18n.file, newDocument);

          const focusedWindow = PDBMLS.extractAndDisplay(baseUrl, i18n, newDocument, targetSpaceKey, location);

          if (focusedWindow !== null) {
            let updateExistingDocument = AJS.$('#projectdoc-continue-document-dialog-checkbox-' + finishDocumentRequestCheckboxId);
            if (updateExistingDocument && updateExistingDocument.is(":checked")) {
              PDBMLS.setPropertyWithValue(baseUrl, pageId, i18n.iteration, "finished", "replace-values", true);
            }
          }
        });
        AJS.$("#projectdoc-continue-document-dialog-cancel").on('click', function (e) {
          e.preventDefault();
          AJS.dialog2("#projectdoc-continue-document-dialog").hide();
        });
      }

      function copyPropertyFromInputField(i18n, propertyName, toDocument, position, propertyControls) {
        const idPart = PDBMLS.replaceAll(propertyName, " ", "_");
        const fieldId = '#projectdoc-continue-document-dialog-field-' + idPart;
        PDBMLS.copyFromField(i18n, fieldId, position, toDocument, propertyName, null, propertyControls);
      }

      function copyPropertyFromTextarea(i18n, propertyName, toDocument, position) {
        const fieldId = '#projectdoc-continue-document-dialog-form textarea[name="' + propertyName + '"]';
        PDBMLS.copyFromField(i18n, fieldId, position, toDocument, propertyName);
      }

      function copyPropertyFromFieldCheckingToggle(i18n, propertyName, toDocument, position, propertyControls) {
        const idPart = PDBMLS.replaceAll(propertyName, " ", "_");
        let toggle = AJS.$('#projectdoc-continue-document-dialog-checkbox-' + idPart);
        if (!toggle || toggle.is(":checked")) {
          copyPropertyFromInputField(i18n, propertyName, toDocument, position, propertyControls);
        }
      }

      function copyStoragePropertyCheckingToggle(i18n, propertyName, storagePropertValue, toDocument, position, propertyControls) {
        const idPart = PDBMLS.replaceAll(propertyName, " ", "_");
        let toggle = AJS.$('#projectdoc-continue-document-dialog-checkbox-' + idPart);
        if (!toggle || toggle.is(":checked")) {
          PDBMLS.copyFromField(i18n, null, position, toDocument, propertyName, storagePropertValue, propertyControls);
        }
      }

      if (pageId) {
        const i18n = PDBMLS.fetchI18n(baseUrl, ["spaceKey", "title", "doctype", "name", "shortDescription", "iteration", "subject", "categories", "tags", "type", "participants", "object", "file", "continuationOf", "absoluteUrl", "projectdoc.doctype.report.last-report"]);
        const objectLabel = i18n["object"];
        const currentDocument = PDBMLS.fetchDocument(baseUrl, pageId, [i18n.spaceKey, i18n.title, i18n.doctype, i18n.name, i18n.shortDescription, i18n.iteration, i18n.subject, i18n.categories, i18n.tags, i18n['type'], i18n.participants, i18n.file], "value");
        if (currentDocument) {
          const currentStorageValues = PDBMLS.fetchDocument(baseUrl, pageId, [objectLabel], "storage");
          const currentRenderedValues = PDBMLS.fetchDocument(baseUrl, pageId, [objectLabel], "html");
          PDBMLS.copyFromTo(currentStorageValues, currentDocument);

          const targetSpaceKey = spaceKey; // "~" + remoteUser;
          const location = "_homepage_";
            // "%7Bhomepage%7D"
            // "{homepage}";
// const targetSpaceKey = "~" + remoteUser;
// const today = PDBMLS.createIsoDateToday();
// const location = today + " " + remoteUser;

          appendDialog(i18n, spaceKey, targetSpaceKey, location, currentDocument, currentRenderedValues);
          AJS.dialog2("#projectdoc-continue-document-dialog").show();
        } else {
          alert("Unknown document for Confluence page with ID '" + pageId + "'.");
        }
      } else {
        alert("Bookmarklet operates only within your Confluence server on projectdoc documents of type 'event', 'todo', or similar types supporting the continuation feature.");
      }
    });
  });