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