You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo-editor.js 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import {htmlEscape} from 'escape-goat';
  2. import {initMarkupContent} from '../markup/content.js';
  3. import {createCodeEditor} from './codeeditor.js';
  4. const {csrfToken} = window.config;
  5. let previewFileModes;
  6. function initEditPreviewTab($form) {
  7. const $tabMenu = $form.find('.tabular.menu');
  8. $tabMenu.find('.item').tab();
  9. const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`);
  10. if ($previewTab.length) {
  11. previewFileModes = $previewTab.data('preview-file-modes').split(',');
  12. $previewTab.on('click', function () {
  13. const $this = $(this);
  14. let context = `${$this.data('context')}/`;
  15. const mode = $this.data('markdown-mode') || 'comment';
  16. const treePathEl = $form.find('input#tree_path');
  17. if (treePathEl.length > 0) {
  18. context += treePathEl.val();
  19. }
  20. context = context.substring(0, context.lastIndexOf('/'));
  21. $.post($this.data('url'), {
  22. _csrf: csrfToken,
  23. mode,
  24. context,
  25. text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val(),
  26. }, (data) => {
  27. const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
  28. $previewPanel.html(data);
  29. initMarkupContent();
  30. });
  31. });
  32. }
  33. }
  34. function initEditDiffTab($form) {
  35. const $tabMenu = $form.find('.tabular.menu');
  36. $tabMenu.find('.item').tab();
  37. $tabMenu.find(`.item[data-tab="${$tabMenu.data('diff')}"]`).on('click', function () {
  38. const $this = $(this);
  39. $.post($this.data('url'), {
  40. _csrf: csrfToken,
  41. context: $this.data('context'),
  42. content: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val(),
  43. }, (data) => {
  44. const $diffPreviewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('diff')}"]`);
  45. $diffPreviewPanel.html(data);
  46. });
  47. });
  48. }
  49. function initEditorForm() {
  50. if ($('.repository .edit.form').length === 0) {
  51. return;
  52. }
  53. initEditPreviewTab($('.repository .edit.form'));
  54. initEditDiffTab($('.repository .edit.form'));
  55. }
  56. function getCursorPosition($e) {
  57. const el = $e.get(0);
  58. let pos = 0;
  59. if ('selectionStart' in el) {
  60. pos = el.selectionStart;
  61. } else if ('selection' in document) {
  62. el.focus();
  63. const Sel = document.selection.createRange();
  64. const SelLength = document.selection.createRange().text.length;
  65. Sel.moveStart('character', -el.value.length);
  66. pos = Sel.text.length - SelLength;
  67. }
  68. return pos;
  69. }
  70. export function initRepoEditor() {
  71. initEditorForm();
  72. $('.js-quick-pull-choice-option').on('change', function () {
  73. if ($(this).val() === 'commit-to-new-branch') {
  74. $('.quick-pull-branch-name').show();
  75. $('.quick-pull-branch-name input').prop('required', true);
  76. } else {
  77. $('.quick-pull-branch-name').hide();
  78. $('.quick-pull-branch-name input').prop('required', false);
  79. }
  80. $('#commit-button').text($(this).attr('button_text'));
  81. });
  82. const $editFilename = $('#file-name');
  83. $editFilename.on('keyup', function (e) {
  84. const $section = $('.breadcrumb span.section');
  85. const $divider = $('.breadcrumb div.divider');
  86. let value;
  87. let parts;
  88. if (e.keyCode === 8 && getCursorPosition($(this)) === 0 && $section.length > 0) {
  89. value = $section.last().find('a').text();
  90. $(this).val(value + $(this).val());
  91. $(this)[0].setSelectionRange(value.length, value.length);
  92. $section.last().remove();
  93. $divider.last().remove();
  94. }
  95. if (e.keyCode === 191) {
  96. parts = $(this).val().split('/');
  97. for (let i = 0; i < parts.length; ++i) {
  98. value = parts[i];
  99. if (i < parts.length - 1) {
  100. if (value.length) {
  101. $(`<span class="section"><a href="#">${htmlEscape(value)}</a></span>`).insertBefore($(this));
  102. $('<div class="divider"> / </div>').insertBefore($(this));
  103. }
  104. } else {
  105. $(this).val(value);
  106. }
  107. $(this)[0].setSelectionRange(0, 0);
  108. }
  109. }
  110. parts = [];
  111. $('.breadcrumb span.section').each(function () {
  112. const element = $(this);
  113. if (element.find('a').length) {
  114. parts.push(element.find('a').text());
  115. } else {
  116. parts.push(element.text());
  117. }
  118. });
  119. if ($(this).val()) parts.push($(this).val());
  120. $('#tree_path').val(parts.join('/'));
  121. }).trigger('keyup');
  122. const $editArea = $('.repository.editor textarea#edit_area');
  123. if (!$editArea.length) return;
  124. (async () => {
  125. const editor = await createCodeEditor($editArea[0], $editFilename[0], previewFileModes);
  126. // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
  127. // to enable or disable the commit button
  128. const $commitButton = $('#commit-button');
  129. const $editForm = $('.ui.edit.form');
  130. const dirtyFileClass = 'dirty-file';
  131. // Disabling the button at the start
  132. if ($('input[name="page_has_posted"]').val() !== 'true') {
  133. $commitButton.prop('disabled', true);
  134. }
  135. // Registering a custom listener for the file path and the file content
  136. $editForm.areYouSure({
  137. silent: true,
  138. dirtyClass: dirtyFileClass,
  139. fieldSelector: ':input:not(.commit-form-wrapper :input)',
  140. change() {
  141. const dirty = $(this).hasClass(dirtyFileClass);
  142. $commitButton.prop('disabled', !dirty);
  143. },
  144. });
  145. // Update the editor from query params, if available,
  146. // only after the dirtyFileClass initialization
  147. const params = new URLSearchParams(window.location.search);
  148. const value = params.get('value');
  149. if (value) {
  150. editor.setValue(value);
  151. }
  152. $commitButton.on('click', (event) => {
  153. // A modal which asks if an empty file should be committed
  154. if ($editArea.val().length === 0) {
  155. $('#edit-empty-content-modal').modal({
  156. onApprove() {
  157. $('.edit.form').trigger('submit');
  158. },
  159. }).modal('show');
  160. event.preventDefault();
  161. }
  162. });
  163. })();
  164. }