projectdoc Toolbox

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.

projectdoc Toolbox version 4.4 required

 

For the latest version of the bookmarklet the version 4.4 of the projectdoc Toolbox is required.

Continuing events with a previous version of this bookmarklet is supported by older versions of the projectdoc Toolbox.


Bookmarklet

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

Ready to use!

Bookmarklets editor

 

The Bookmarklets Extension contains a doctype that provides a bookmarklets editor to edit and create bookmarklets.

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 = 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}";
// 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.");
      }
    });
  });

Work-in-progress

 

Note that the bookmarklets we present on this page are work-in-progress.

Currently we even refer to the master branch of the bookmark's JavaScript code. This will change in future once we have proper releases for our bookmarks project.

While we use the code in our daily work, your use case may be different. Therefore please check the code and maybe fork the projectdoc Bookmarklets Project to adjust the code according to your requirements.