Diaspora reply button

A script to add 'reply' links to comments on default Diaspora* (desktop) website interfaces, which will insert the current comment's author's handle in the new comment textarea.

  1. // ==UserScript==
  2. // @name Diaspora reply button
  3. // @description A script to add 'reply' links to comments on default Diaspora* (desktop) website interfaces, which will insert the current comment's author's handle in the new comment textarea.
  4. // @author Filip H.F. "FiXato" Slagter
  5. // @version 5
  6. // @include /^https:\/\/(www\.)?(plu|joindia)spora\.com/
  7. // @run-at document-idle
  8. // @grant none
  9. // @namespace https://github.com/FiXato
  10. // ==/UserScript==
  11. // esversion: 6
  12. // Latest version at: https://gist.github.com/FiXato/fed7eca2e044705a3c14253bf2184335
  13.  
  14. window.debugUserScript=false;
  15. function consoleDebug() {
  16. if (window.debugUserScript) {
  17. return console.log.apply(null, arguments);
  18. }
  19. }
  20.  
  21. // Function by Dmitriy Kubyshkin and copied (with minor variable name modifications) from https://www.everythingfrontend.com/posts/insert-text-into-textarea-at-cursor-position.html
  22. function insertAtCursor(textarea_element, textToInsert) {
  23. const isSuccess = document.execCommand("insertText", false, textToInsert);
  24.  
  25. // Firefox (non-standard method)
  26. if (!isSuccess && typeof textarea_element.setRangeText === "function") {
  27. const start = textarea_element.selectionStart;
  28. textarea_element.setRangeText(textToInsert);
  29. // update cursor to be at the end of insertion
  30. textarea_element.selectionStart = textarea_element.selectionEnd = start + textToInsert.length;
  31.  
  32. // Notify any possible listeners of the change
  33. const e = document.createEvent("UIEvent");
  34. e.initEvent("input", true, false);
  35. textarea_element.dispatchEvent(e);
  36. }
  37. }
  38.  
  39. function get_hovercard_data(profile_url, link_element, callback_fn) {
  40. consoleDebug('getting hovercard data for profile link: ', profile_url);
  41. var request = new XMLHttpRequest();
  42. request.open('GET', profile_url + '/hovercard.json', true);
  43.  
  44. request.onload = function() {
  45. if (request.status >= 200 && request.status < 400) {
  46. // Success!
  47. var data = JSON.parse(request.responseText);
  48. consoleDebug("hovercard JSON: ", request.responseText);
  49. consoleDebug("hovercard data: ", data);
  50. callback_fn(data['diaspora_id'], link_element);
  51. } else {
  52. consoleDebug("Unsupported HTTP status while retrieving hovercard data: ", request.status, request.responseText);
  53. }
  54. };
  55.  
  56. request.onerror = function() {
  57. // There was a connection error of some sort
  58. consoleDebug("Request error while retrieving hovercard data: ", request.status, request.responseText);
  59. };
  60.  
  61. request.send();
  62. }
  63.  
  64.  
  65. function insert_handle_in_comment(handle, link_element) {
  66. var new_comment_box = link_element.closest('.comment_stream').querySelector('.new-comment .comment-box');
  67. consoleDebug("Inserting handle at ", handle, new_comment_box);
  68. insertAtCursor(new_comment_box, '@{' + handle + '}');
  69. new_comment_box.focus();
  70. }
  71.  
  72. function add_reply_link(comment_element) {
  73. consoleDebug("Comment: ", comment_element);
  74. consoleDebug(comment_element.dataset);
  75. if (comment_element.dataset.profile_link) {
  76. consoleDebug('Comment already has a profile link: ', comment_element.dataset.profile_link, comment_element);
  77. return;
  78. }
  79. else {
  80. consoleDebug("No profile link");
  81. comment_element.dataset.profile_link = comment_element.querySelector('.author-name')['href'];
  82. consoleDebug('Profile link added to comment: ', comment_element.dataset.profile_link, comment_element);
  83. }
  84. var reply_link = document.createElement('a');
  85. var new_comment_form = comment_element.closest('.comment_stream').querySelector('form.new-comment');
  86. consoleDebug("new comment form: ", comment_element, new_comment_form);
  87. reply_link['href'] = '#' + new_comment_form['id'];
  88. reply_link.appendChild(document.createTextNode("reply"));
  89. reply_link.dataset.profile_link = comment_element.dataset.profile_link;
  90. consoleDebug('Reply Link: ', reply_link);
  91. reply_link.addEventListener('click', function (e) {
  92. if (!this.dataset.handle) {
  93. consoleDebug('Could not find handle in dataset. Requesting from hovercard url', this);
  94. data = get_hovercard_data(this.dataset.profile_link, this, insert_handle_in_comment);
  95. }
  96. else {
  97. insert_handle_in_comment(this.dataset.handle, this);
  98. }
  99. });
  100. consoleDebug('Reply Link with event listener: ', reply_link);
  101.  
  102. var control_icons = comment_element.querySelector('.control-icons');
  103. consoleDebug('control-icons: ', control_icons);
  104. control_icons.appendChild(reply_link);
  105. }
  106.  
  107.  
  108. window.mainContainerQueryPath = "#main-stream > div, #profile_container .row, #container";
  109.  
  110. function process_comments() {
  111. var comments = document.querySelectorAll('.comment_stream .comments .comment');
  112. if (comments) {
  113. consoleDebug(comments.length);
  114. consoleDebug("Adding reply link to comments: ", comments);
  115. comments.forEach(comment_element => add_reply_link(comment_element));
  116. }
  117. else {
  118. consoleDebug("Could not find comments");
  119. }
  120. }
  121.  
  122. (function() {
  123. 'use strict';
  124. var stream = document.querySelector(window.mainContainerQueryPath);
  125. var callback = function(mutationRecords) {
  126. consoleDebug("mutationRecords callback");
  127. stream = document.querySelector(window.mainContainerQueryPath);
  128.  
  129. mutationRecords.forEach(function(mutationRecord) {
  130. if (mutationRecord && mutationRecord.type && mutationRecord.type == "childList") {
  131. if (mutationRecord.target) {
  132. if (!mutationRecord.target.className.includes('timeago')) {
  133. consoleDebug(mutationRecord);
  134. if(mutationRecord.target.className && mutationRecord.target.className.includes('comments')) {
  135. process_comments();
  136. }
  137. }
  138. }
  139. if (mutationRecord.previousSibling && mutationRecord.previousSibling.className && mutationRecord.previousSibling.className.includes('stream-element') && mutationRecord.previousSibling.className.includes('loaded')) {
  140. consoleDebug("Calling process_comments()");
  141. process_comments();
  142. }
  143. }
  144. });
  145. }
  146. if( stream ) {
  147. new MutationObserver(callback).observe(stream, {
  148. attributes: true,
  149. attributeOldValue: true,
  150. childList: true,
  151. subtree: true
  152. })
  153. }
  154. })();