選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

repo-editor.js 6.0KB

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