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