forgejo/web_src/js/features/repo-editor.js
silverwind 2cf91d58e7
[PORT] Enable no-jquery/no-parse-html-literal and fix violation (gitea#31684)
Tested it, path segment creation works just like before.

---

Conflict resolution: trivial, also ported code from
https://github.com/go-gitea/gitea/pull/31283
2024-07-28 16:52:02 +02:00

204 lines
6.3 KiB
JavaScript

import $ from 'jquery';
import {htmlEscape} from 'escape-goat';
import {createCodeEditor} from './codeeditor.js';
import {hideElem, showElem, createElementFromHTML} from '../utils/dom.js';
import {initMarkupContent} from '../markup/content.js';
import {attachRefIssueContextPopup} from './contextpopup.js';
import {POST} from '../modules/fetch.js';
function initEditPreviewTab($form) {
const $tabMenu = $form.find('.tabular.menu');
$tabMenu.find('.item').tab();
const $previewTab = $tabMenu.find(
`.item[data-tab="${$tabMenu.data('preview')}"]`,
);
if ($previewTab.length) {
$previewTab.on('click', async function () {
const $this = $(this);
let context = `${$this.data('context')}/`;
const mode = $this.data('markup-mode') || 'comment';
const $treePathEl = $form.find('input#tree_path');
if ($treePathEl.length > 0) {
context += $treePathEl.val();
}
context = context.substring(0, context.lastIndexOf('/'));
const formData = new FormData();
formData.append('mode', mode);
formData.append('context', context);
formData.append(
'text',
$form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val(),
);
formData.append('file_path', $treePathEl.val());
try {
const response = await POST($this.data('url'), {data: formData});
const data = await response.text();
const $previewPanel = $form.find(
`.tab[data-tab="${$tabMenu.data('preview')}"]`,
);
renderPreviewPanelContent($previewPanel, data);
} catch (error) {
console.error('Error:', error);
}
});
}
}
function initEditorForm() {
const $form = $('.repository .edit.form');
if (!$form) return;
initEditPreviewTab($form);
}
function getCursorPosition($e) {
const el = $e.get(0);
let pos = 0;
if ('selectionStart' in el) {
pos = el.selectionStart;
} else if ('selection' in document) {
el.focus();
const Sel = document.selection.createRange();
const SelLength = document.selection.createRange().text.length;
Sel.moveStart('character', -el.value.length);
pos = Sel.text.length - SelLength;
}
return pos;
}
export function initRepoEditor() {
initEditorForm();
$('.js-quick-pull-choice-option').on('change', function () {
if ($(this).val() === 'commit-to-new-branch') {
showElem('.quick-pull-branch-name');
document.querySelector('.quick-pull-branch-name input').required = true;
} else {
hideElem('.quick-pull-branch-name');
document.querySelector('.quick-pull-branch-name input').required = false;
}
$('#commit-button').text(this.getAttribute('button_text'));
});
const joinTreePath = ($fileNameEl) => {
const parts = [];
$('.breadcrumb span.section').each(function () {
const $element = $(this);
if ($element.find('a').length) {
parts.push($element.find('a').text());
} else {
parts.push($element.text());
}
});
if ($fileNameEl.val()) parts.push($fileNameEl.val());
$('#tree_path').val(parts.join('/'));
};
const $editFilename = $('#file-name');
$editFilename.on('input', function () {
const parts = $(this).val().split('/');
if (parts.length > 1) {
for (let i = 0; i < parts.length; ++i) {
const value = parts[i];
if (i < parts.length - 1) {
if (value.length) {
$editFilename[0].before(
createElementFromHTML(
`<span class="section"><a href="#">${htmlEscape(value)}</a></span>`,
),
);
$editFilename[0].before(
createElementFromHTML(`<div class="breadcrumb-divider">/</div>`),
);
}
} else {
$(this).val(value);
}
this.setSelectionRange(0, 0);
}
}
joinTreePath($(this));
});
$editFilename.on('keydown', function (e) {
const $section = $('.breadcrumb span.section');
// Jump back to last directory once the filename is empty
if (
e.code === 'Backspace' &&
getCursorPosition($(this)) === 0 &&
$section.length > 0
) {
e.preventDefault();
const $divider = $('.breadcrumb .breadcrumb-divider');
const value = $section.last().find('a').text();
$(this).val(value + $(this).val());
this.setSelectionRange(value.length, value.length);
$section.last().remove();
$divider.last().remove();
joinTreePath($(this));
}
});
const $editArea = $('.repository.editor textarea#edit_area');
if (!$editArea.length) return;
(async () => {
const editor = await createCodeEditor($editArea[0], $editFilename[0]);
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
// to enable or disable the commit button
const commitButton = document.getElementById('commit-button');
const $editForm = $('.ui.edit.form');
const dirtyFileClass = 'dirty-file';
// Disabling the button at the start
if ($('input[name="page_has_posted"]').val() !== 'true') {
commitButton.disabled = true;
}
// Registering a custom listener for the file path and the file content
$editForm.areYouSure({
silent: true,
dirtyClass: dirtyFileClass,
fieldSelector: ':input:not(.commit-form-wrapper :input)',
change($form) {
const dirty = $form[0]?.classList.contains(dirtyFileClass);
commitButton.disabled = !dirty;
},
});
// Update the editor from query params, if available,
// only after the dirtyFileClass initialization
const params = new URLSearchParams(window.location.search);
const value = params.get('value');
if (value) {
editor.setValue(value);
}
commitButton?.addEventListener('click', (e) => {
// A modal which asks if an empty file should be committed
if (!$editArea.val()) {
$('#edit-empty-content-modal')
.modal({
onApprove() {
$('.edit.form').trigger('submit');
},
})
.modal('show');
e.preventDefault();
}
});
})();
}
export function renderPreviewPanelContent($panelPreviewer, data) {
$panelPreviewer.html(data);
initMarkupContent();
const $refIssues = $panelPreviewer.find('p .ref-issue');
attachRefIssueContextPopup($refIssues);
}