Source: clientModules/writeChanges.js

  1. /**
  2. * @file This file makes table cells editable and send the edited content to a server and create a GitHub issue for each edit. It uses the Octokit library to interact with the GitHub API and the awesome-notifications library to display notifications to the user.
  3. It also uses a MutationObserver to observe changes in the element. When a change is detected, it collects the data of the edited cell (like row, column, row number, column number, column name, proposed text, term) and stores it in the mutation object.
  4. * @author Kor Dwarshuis
  5. * @version 1.0.0
  6. * @since 2023-03-15
  7. */
  8. import { Octokit, App } from 'octokit';
  9. // https://f3oall.github.io/awesome-notifications/docs/why-such-awesome
  10. // https://www.npmjs.com/package/awesome-notifications
  11. // https://github.com/f3oall/awesome-notifications#readme
  12. // import AWN from 'awesome-notifications';
  13. import AWN from './libs/awesome-notifications.js';
  14. // Initialize instance of AWN
  15. let notifier = new AWN({
  16. maxNotifications: 6,
  17. durations: {
  18. alert: 0,
  19. success: 4000,
  20. },
  21. icons: {
  22. enabled: false,
  23. },
  24. });
  25. const writeChanges = (element) => {
  26. const el = document.querySelector(element);
  27. const buttonTextEdit = 'Edit';
  28. const buttonTextSave = 'Send';
  29. const buttonTextCancel = 'Cancel';
  30. let explanationAboutGithubIssueShown = false;
  31. const domainReceivingChanges =
  32. 'https://dwarshuis.com/test/wot-terms/php_scripts/saveEdits.php';
  33. if (el !== null) {
  34. write();
  35. }
  36. function write() {
  37. const makeEditable = el;
  38. let mutation = {};
  39. // // Create an edit/save button and insert before the element we want to edit
  40. // const editSaveButton = document.createElement('button');
  41. // editSaveButton.classList.add('button');
  42. // editSaveButton.classList.add('button--secondary');
  43. // editSaveButton.classList.add('margin--md');
  44. // editSaveButton.classList.add('edit-save');
  45. // editSaveButton.innerText = buttonTextEdit;
  46. // el.parentNode.insertBefore(editSaveButton, el);
  47. // editSaveButton.addEventListener('click', makeContentEditable);
  48. // Create an edit/save button in every table cell
  49. const tableCells = document.querySelectorAll('.googlesheet td');
  50. tableCells.forEach((cell) => {
  51. if (cell.dataset.columnnr !== '0') {
  52. // Surround the cell content with a div
  53. cell.innerHTML =
  54. '<div class="cell-content">' + cell.innerHTML + '</div>';
  55. const div = document.createElement('div');
  56. div.classList.add('buttons');
  57. cell.appendChild(div);
  58. const editSaveButton = document.createElement('button');
  59. editSaveButton.classList.add('button');
  60. editSaveButton.classList.add('button--secondary');
  61. editSaveButton.classList.add('margin--md');
  62. editSaveButton.classList.add('edit-save');
  63. editSaveButton.innerText = buttonTextEdit;
  64. div.appendChild(editSaveButton);
  65. editSaveButton.addEventListener('click', makeTableCellEditable);
  66. const cancelButton = document.createElement('button');
  67. cancelButton.classList.add('button');
  68. cancelButton.classList.add('button--secondary');
  69. cancelButton.classList.add('margin--md');
  70. cancelButton.classList.add('cancel');
  71. cancelButton.innerText = buttonTextCancel;
  72. div.appendChild(cancelButton);
  73. cancelButton.addEventListener('click', cancelTableCellEditable);
  74. }
  75. });
  76. function makeTableCellEditable() {
  77. if (this.parentElement.parentElement.contentEditable !== 'true') {
  78. this.parentElement.parentElement.contentEditable = 'true';
  79. this.parentElement.parentElement.classList.add('editable');
  80. this.parentElement.parentElement.focus();
  81. this.innerText = buttonTextSave;
  82. if (explanationAboutGithubIssueShown === false) {
  83. notifier.info(
  84. `After editing, click the “${buttonTextSave}” button, and a Github issue will be generated.`
  85. );
  86. explanationAboutGithubIssueShown = true;
  87. }
  88. } else {
  89. this.parentElement.parentElement.contentEditable = 'false';
  90. this.innerText = buttonTextEdit;
  91. sendContent();
  92. }
  93. }
  94. function cancelTableCellEditable() {
  95. this.parentElement.parentElement.contentEditable = 'false';
  96. this.parentElement.parentElement.classList.remove('editable');
  97. this.parentElement.querySelector('button.edit-save').innerText =
  98. buttonTextEdit;
  99. }
  100. async function sendContent() {
  101. var formData = new FormData();
  102. formData.append('content', JSON.stringify(mutation));
  103. /**
  104. * Write to a textfile on a domain
  105. */
  106. // TODO: improve fetch
  107. fetch(domainReceivingChanges, { method: 'POST', body: formData });
  108. // .then(
  109. // function (response) {
  110. // return response.text();
  111. // }
  112. // );
  113. // .then(function (body) {
  114. // });
  115. // // Octokit.js
  116. // // https://github.com/octokit/core.js#readme
  117. // const octokit = new Octokit({
  118. // auth: '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',
  119. // });
  120. // octokit.request('POST /repos/kordwarshuis/WOT-terms-edits/dispatches', {
  121. // owner: 'kordwarshuis',
  122. // repo: 'WOT-terms-edits',
  123. // event_type: 'edit',
  124. // client_payload: {
  125. // unit: false,
  126. // integration: true,
  127. // },
  128. // headers: {
  129. // 'X-GitHub-Api-Version': '2022-11-28',
  130. // },
  131. // });
  132. /**
  133. * Create an issue on Github
  134. */
  135. let auth = prompt('Enter token');
  136. // Initialize the Octokit client
  137. const octokit = new Octokit({
  138. auth: auth,
  139. });
  140. // Create the issue payload
  141. const payload = {
  142. owner: 'kordwarshuis',
  143. repo: 'WOT-terms-edits',
  144. title: `New edit for “${mutation.columnname}” for the term: “${mutation.term}”.`,
  145. body: `An edit has been made in column “${mutation.columnname}” for the term: “${mutation.term}”.\n\nThe new text is: “${mutation.proposedText}”\n\n(Column: ${mutation.columnnr}, Row: ${mutation.rownr})`,
  146. };
  147. // Send the request to create the issue
  148. const response = await octokit.rest.issues.create(payload);
  149. let onOk = () => {
  150. // notifier.info('You pressed OK');
  151. };
  152. notifier.confirm(
  153. `A new issue has been created on Github at: <a target="_blank" rel="noopener" href="${response.data.html_url}">${response.data.html_url}</a>`,
  154. onOk,
  155. false,
  156. {
  157. labels: {
  158. confirm: 'Info',
  159. },
  160. }
  161. );
  162. }
  163. // https://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/
  164. // TODO: implement observer.disconnect();
  165. const observer = new MutationObserver((mutationRecords) => {
  166. // Collect the data like row, column, rownr, columnnr, columnname, proposedText, term of the edited cell
  167. mutation.row =
  168. mutationRecords[0].target.parentElement.parentElement.dataset.row;
  169. mutation.rownr =
  170. mutationRecords[0].target.parentElement.parentElement.dataset.rownr;
  171. mutation.column =
  172. mutationRecords[0].target.parentElement.parentElement.dataset.column;
  173. mutation.columnnr =
  174. mutationRecords[0].target.parentElement.parentElement.dataset.columnnr;
  175. mutation.columnname = document.querySelectorAll(
  176. `.googlesheet th[data-columnnr]`
  177. )[
  178. mutationRecords[0].target.parentElement.parentElement.dataset.columnnr
  179. ].innerText;
  180. // The text that is being edited
  181. mutation.proposedText =
  182. mutationRecords[0].target.parentElement.parentElement.innerText;
  183. // Remove the edit button text from the text that is being edited
  184. mutation.proposedText = mutation.proposedText.substring(
  185. 0,
  186. mutation.proposedText.length -
  187. buttonTextSave.length -
  188. buttonTextCancel.length -
  189. 1
  190. );
  191. // The term that is being edited
  192. mutation.term = document.querySelectorAll(
  193. `.googlesheet tr[data-rownr="${mutation.rownr}"] td[data-columnnr="4"]`
  194. )[0].innerText;
  195. // Remove the edit button text from the term
  196. mutation.term = mutation.term.substring(
  197. 0,
  198. mutation.term.length -
  199. document.querySelectorAll(
  200. `.googlesheet tr[data-rownr="${mutation.rownr}"] td[data-columnnr="4"] button.cancel`
  201. )[0].innerText.length -
  202. document.querySelectorAll(
  203. `.googlesheet tr[data-rownr="${mutation.rownr}"] td[data-columnnr="4"] button.edit-save`
  204. )[0].innerText.length -
  205. 1
  206. );
  207. });
  208. observer.observe(el, {
  209. characterData: true,
  210. subtree: true,
  211. });
  212. }
  213. };
  214. export function onRouteDidUpdate({ location, previousLocation }) {
  215. // Don't execute if we are still on the same page; the lifecycle may be fired
  216. // because the hash changes (e.g. when navigating between headings)
  217. // if (location.pathname === previousLocation?.pathname) return;
  218. writeChanges('.googlesheet');
  219. }