From de6e7cdeb80380ff7fe2f561e4020709237fb9e7 Mon Sep 17 00:00:00 2001
From: Mark Carver <mark.carver@me.com>
Date: Thu, 25 Aug 2016 16:01:40 -0500
Subject: [PATCH] Issue #1673278 by markcarver: Implement User Enhancements on
 Drupal.org and port Dreditor features

---
 .../drupalorg_project.user_enhancements.inc        |  60 +++++++++
 .../drupalorg.issue.clone/drupalorg.issue.clone.js |  73 +++++++++++
 .../drupalorg.issue/drupalorg.issue.js             | 142 +++++++++++++++++++++
 .../drupalorg.patch.filename.suggestion.css        |   3 +
 .../drupalorg.patch.filename.suggestion.js         |  35 +++++
 5 files changed, 313 insertions(+)
 create mode 100644 drupalorg_project/drupalorg_project.user_enhancements.inc
 create mode 100644 drupalorg_project/user_enhancements/drupalorg.issue.clone/drupalorg.issue.clone.js
 create mode 100644 drupalorg_project/user_enhancements/drupalorg.issue/drupalorg.issue.js
 create mode 100644 drupalorg_project/user_enhancements/drupalorg.patch.filename.suggestion/drupalorg.patch.filename.suggestion.css
 create mode 100644 drupalorg_project/user_enhancements/drupalorg.patch.filename.suggestion/drupalorg.patch.filename.suggestion.js

diff --git a/drupalorg_project/drupalorg_project.user_enhancements.inc b/drupalorg_project/drupalorg_project.user_enhancements.inc
new file mode 100644
index 0000000..5b47cec
--- /dev/null
+++ b/drupalorg_project/drupalorg_project.user_enhancements.inc
@@ -0,0 +1,60 @@
+<?php
+/**
+ * @file
+ * user_enhancements hook implementations for the drupalorg_project module.
+ */
+
+/**
+ * Implements hook_user_enhancements_group_info().
+ */
+function drupalorg_project_user_enhancements_group_info() {
+  return array(
+    'issues'    => array('title' => t('Issues')),
+    'patches'   => array('title' => t('Patches')),
+    'projects'  => array('title' => t('Projects')),
+  );
+}
+
+/**
+ * Implements hook_user_enhancements_info().
+ */
+function drupalorg_project_user_enhancements_info() {
+  $enhancements['drupalorg.issue'] = array(
+    'ui' => FALSE,
+    'dependencies' => array(
+      array('user_enhancements', 'user.enhancements.core'),
+    ),
+  );
+
+  $enhancements['drupalorg.issue.clone'] = array(
+    'group' => 'issues',
+    'title' => t('Clone this issue'),
+    'description' => t('Adds a "Clone this issue" button at the bottom of the issue meta data block in the sidebar. When clicked it will open a new tab/window to create a new issue in that project and populate the current issue values into that new tab/window.'),
+    'dependencies' => array(
+      array('user_enhancements', 'drupalorg.issue'),
+    ),
+    'conditions' => array(
+      array(
+        'entity_type' => 'node',
+        'entity_bundle' => 'project_issue',
+      ),
+    ),
+  );
+
+  $enhancements['drupalorg.patch.filename.suggestion'] = array(
+    'group' => 'patches',
+    'title' => t('Patch filename suggestion'),
+    'description' => t('Adds a "Patch filename suggestion" button at the top of the "Files" fields on an issue. When clicked, it will suggest a patch filename based on the current state of the issue.'),
+    'dependencies' => array(
+      array('user_enhancements', 'drupalorg.issue'),
+    ),
+    'conditions' => array(
+      array(
+        'entity_type' => 'node',
+        'entity_bundle' => 'project_issue',
+      ),
+    ),
+  );
+
+  return $enhancements;
+}
diff --git a/drupalorg_project/user_enhancements/drupalorg.issue.clone/drupalorg.issue.clone.js b/drupalorg_project/user_enhancements/drupalorg.issue.clone/drupalorg.issue.clone.js
new file mode 100644
index 0000000..778568d
--- /dev/null
+++ b/drupalorg_project/user_enhancements/drupalorg.issue.clone/drupalorg.issue.clone.js
@@ -0,0 +1,73 @@
+(function ($, Drupal, Enhancements) {
+  'use strict';
+
+  /**
+   * Attach clone issue button to issues.
+   */
+  Drupal.behaviors.enhancementIssueClone = {
+    attach: function (context) {
+      var _window = window;
+      var $context = $(context);
+      var $issueUpdate = $context.find('.region-sidebar-first .issue-update');
+      $issueUpdate.once('enhancement.issue.clone', function () {
+        $('<div class="update-link"><a href="#" class="issue-button">' + Drupal.t('Clone this issue') + '</a></div>')
+          .appendTo($issueUpdate).find('a')
+          .bind('click.enhancement.clone', function (e) {
+            e.preventDefault();
+            e.stopPropagation();
+
+            // Get the project.
+            var project = Enhancements.issue.getProjectShortName();
+            if (!project) {
+              return;
+            }
+
+            // Open a new window.
+            var w = _window.open('/node/add/project-issue/' + project + '#project-issue-node-form', '_blank');
+            // $(w).bind('load') does not actually bind to the new window "load"
+            // event. This may be on purpose or a bug with the currently used
+            // jQuery version on d.o (1.4.4).
+            w.addEventListener('load', function () {
+              // Retrieve the DOM of the newly created window.
+              var $document = $(w.document);
+              $document.ready(function () {
+                var parentNid = Enhancements.issue.getNid();
+                var $parentForm = $context.find('#project-issue-node-form');
+                var $newForm = $document.contents().find('#project-issue-node-form');
+                var selector, selectors = [
+                  '#edit-title',
+                  '#edit-body-und-0-value',
+                  '#edit-field-issue-category-und',
+                  '#edit-field-issue-priority-und',
+                  '#edit-field-issue-status-und',
+                  '#edit-field-issue-version-und',
+                  '#edit-field-issue-component-und',
+                  '#edit-field-issue-assigned-und',
+                  '#edit-taxonomy-vocabulary-9-und'
+                ];
+                for (selector in selectors) {
+                  $newForm.find(selectors[selector]).val($parentForm.find(selectors[selector]).val());
+                }
+
+                // Prepend body with "Follow-up to ..." line.
+                var $body = $newForm.find('#edit-body-und-0-value');
+                $body.val('Follow-up to [#' + parentNid + ']\n\n' + $body.val());
+
+                // Add originating issue was parent issue relationship.
+                $newForm.find('#edit-field-issue-parent-und-0-target-id')
+                  .val($parentForm.find('#edit-title').val() + ' (' + parentNid + ')');
+
+
+                // Ensure all fieldsets are expanded.
+                $newForm.find('.collapsed').removeClass('collapsed');
+
+                // Focus on the new issue title so users can enter it.
+                $newForm.find('#edit-title').focus();
+              });
+            }, false);
+          });
+      });
+    }
+  };
+
+})(jQuery, Drupal, Drupal.userEnhancements);
diff --git a/drupalorg_project/user_enhancements/drupalorg.issue/drupalorg.issue.js b/drupalorg_project/user_enhancements/drupalorg.issue/drupalorg.issue.js
new file mode 100644
index 0000000..628c5d2
--- /dev/null
+++ b/drupalorg_project/user_enhancements/drupalorg.issue/drupalorg.issue.js
@@ -0,0 +1,142 @@
+(function ($, Drupal, Enhancements) {
+  'use strict';
+
+  Enhancements.issue = {};
+
+  /**
+   * Gets the issue node id.
+   */
+  Enhancements.issue.getNid = function() {
+    var href = $('#tabs a:first').attr('href');
+    if (href.length) {
+      return href.match(/(?:node|comment\/reply)\/(\d+)/)[1];
+    }
+    return false;
+  };
+
+  /**
+   * Returns the next comment number for the current issue.
+   */
+  Enhancements.issue.getNewCommentNumber = function() {
+    // Get comment count.
+    var lastCommentNumber = $('.comments .comment:last .permalink').text().match(/\d+$/);
+    return (lastCommentNumber ? parseInt(lastCommentNumber[0], 10) : 0) + 1;
+  };
+
+  /**
+   * Gets the issue title.
+   */
+  Enhancements.issue.getIssueTitle = function() {
+    return $('#page-subtitle').text() || '';
+  };
+
+  /**
+   * Gets the project shortname.
+   *
+   * @return
+   *   Return false when using the preview mode since the breadcrumb is not
+   *   included in the preview mode.
+   */
+  Enhancements.issue.getProjectShortName = function() {
+    // Retrieve project from breadcrumb.
+    var project = $('.breadcrumb a:eq(0)').attr('href');
+
+    // @todo The comment preview page does not contain a breadcrumb and also
+    //   does not expose the project name anywhere else.
+    if (project) {
+      // The Drupal (core) project breadcrumb does not contain a project page link.
+      if (project === '/project/issues/drupal') {
+        project = 'drupal';
+      }
+      else {
+        project = project.substr(9);
+      }
+    }
+    else {
+      project = false;
+    }
+
+    return project;
+  };
+
+  Enhancements.issue.getSelectedComponent = function() {
+    // Retrieve component from the comment form selected option label.
+    return $(':input[name*="issue_component"] :selected').text();
+  };
+
+  /**
+   * Gets the selected version.
+   *
+   * Variations:
+   *   7.x
+   *   7.x-dev
+   *   7.x-alpha1
+   *   7.20
+   *   7.x-1.x
+   *   7.x-1.12
+   *   7.x-1.x
+   *   - 8.x issues -
+   *   - Any -
+   *   All-versions-4.x-dev
+   */
+  Enhancements.issue.getSelectedVersion = function() {
+    // Retrieve version from the comment form selected option label.
+    var version = $(':input[name*="issue_version"] :selected').text();
+    return version;
+  };
+
+  /**
+   * Gets the selected core version.
+   *
+   * Variations:
+   *   7.x
+   *   7.20
+   */
+  Enhancements.issue.getSelectedVersionCore = function() {
+    var version = Enhancements.issue.getSelectedVersion();
+    var matches = version.match(/^(\d+\.[x\d]+)/);
+    if (matches) {
+      return matches[0];
+    }
+    else {
+      return false;
+    }
+  };
+
+  /**
+   * Gets the selected contrib version.
+   *
+   * Variations:
+   *   1.x
+   *   1.2
+   */
+  Enhancements.issue.getSelectedVersionContrib = function() {
+    var version = Enhancements.issue.getSelectedVersion();
+    var matches = version.match(/^\d+\.x-(\d+\.[x\d]+)/);
+    if (matches) {
+      return matches[1];
+    }
+    else {
+      return false;
+    }
+  };
+
+  /**
+   * Gets the selected core + contrib version.
+   *
+   * Variations:
+   *   7.x-1.x
+   *   7.x-1.2
+   */
+  Enhancements.issue.getSelectedVersionCoreContrib = function() {
+    var version = Enhancements.issue.getSelectedVersion();
+    var matches = version.match(/^(\d+\.x-\d+\.[x\d]+)/);
+    if (matches) {
+      return matches[0];
+    }
+    else {
+      return false;
+    }
+  };
+
+})(jQuery, Drupal, Drupal.userEnhancements);
diff --git a/drupalorg_project/user_enhancements/drupalorg.patch.filename.suggestion/drupalorg.patch.filename.suggestion.css b/drupalorg_project/user_enhancements/drupalorg.patch.filename.suggestion/drupalorg.patch.filename.suggestion.css
new file mode 100644
index 0000000..723d688
--- /dev/null
+++ b/drupalorg_project/user_enhancements/drupalorg.patch.filename.suggestion/drupalorg.patch.filename.suggestion.css
@@ -0,0 +1,3 @@
+.field-name-field-issue-files .form-type-managed-file > .enhancement-button {
+    margin: 0 0 20px;
+}
diff --git a/drupalorg_project/user_enhancements/drupalorg.patch.filename.suggestion/drupalorg.patch.filename.suggestion.js b/drupalorg_project/user_enhancements/drupalorg.patch.filename.suggestion/drupalorg.patch.filename.suggestion.js
new file mode 100644
index 0000000..dd3d18a
--- /dev/null
+++ b/drupalorg_project/user_enhancements/drupalorg.patch.filename.suggestion/drupalorg.patch.filename.suggestion.js
@@ -0,0 +1,35 @@
+(function ($, Drupal, Enhancements) {
+  'use strict';
+
+  /**
+   * Suggest a filename for patches to upload in an issue.
+   */
+  Drupal.behaviors.enhancementPatchFilenameSuggestion = {
+    attach: function (context) {
+      $('.field-name-field-issue-files .form-type-managed-file', context).once('enhancement.patch.filename.suggestion', function () {
+        var $button = $('<input type="submit" class="enhancement-button form-submit" value="' + Drupal.t('Patch filename suggestion') + '" />');
+        $button.prependTo(this);
+        $button.click(function(e) {
+          e.preventDefault();
+          e.stopPropagation();
+
+          function truncateString(str, n, useWordBoundary) {
+            var toLong = str.length > n,
+              s_ = toLong ? str.substr(0, n - 1) : str;
+            return useWordBoundary && toLong ? s_.substr(0, s_.lastIndexOf(' ')) : s_;
+          }
+
+          // Truncate and remove a heading/trailing underscore.
+          var title = truncateString(Enhancements.issue.getIssueTitle() || '', 25, true);
+          var patchName = title.replace(/[^a-zA-Z0-9]+/g, '_').replace(/(^_|_$)/, '').toLowerCase();
+          var nid = Enhancements.issue.getNid() || 0;
+          if (nid !== 0) {
+            patchName += (patchName.length ? '-' : '') + nid;
+          }
+          window.prompt("Please use this value", patchName + '-' + Enhancements.issue.getNewCommentNumber() + '.patch');
+        });
+      });
+    }
+  };
+
+})(jQuery, Drupal, Drupal.userEnhancements);
-- 
2.8.3

