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.9KB

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