| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- import {html, htmlRaw} from '../utils/html.ts';
- import {createCodeEditor} from './codeeditor.ts';
- import {hideElem, queryElems, showElem, createElementFromHTML} from '../utils/dom.ts';
- import {attachRefIssueContextPopup} from './contextpopup.ts';
- import {POST} from '../modules/fetch.ts';
- import {initDropzone} from './dropzone.ts';
- import {confirmModal} from './comp/ConfirmModal.ts';
- import {applyAreYouSure, ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
- import {fomanticQuery} from '../modules/fomantic/base.ts';
- import {submitFormFetchAction} from './common-fetch-action.ts';
-
- function initEditPreviewTab(elForm: HTMLFormElement) {
- const elTabMenu = elForm.querySelector('.repo-editor-menu');
- fomanticQuery(elTabMenu.querySelectorAll('.item')).tab();
-
- const elPreviewTab = elTabMenu.querySelector('a[data-tab="preview"]');
- const elPreviewPanel = elForm.querySelector('.tab[data-tab="preview"]');
- if (!elPreviewTab || !elPreviewPanel) return;
-
- elPreviewTab.addEventListener('click', async () => {
- const elTreePath = elForm.querySelector<HTMLInputElement>('input#tree_path');
- const previewUrl = elPreviewTab.getAttribute('data-preview-url');
- const previewContextRef = elPreviewTab.getAttribute('data-preview-context-ref');
- let previewContext = `${previewContextRef}/${elTreePath.value}`;
- previewContext = previewContext.substring(0, previewContext.lastIndexOf('/'));
- const formData = new FormData();
- formData.append('mode', 'file');
- formData.append('context', previewContext);
- formData.append('text', elForm.querySelector<HTMLTextAreaElement>('.tab[data-tab="write"] textarea').value);
- formData.append('file_path', elTreePath.value);
- const response = await POST(previewUrl, {data: formData});
- const data = await response.text();
- renderPreviewPanelContent(elPreviewPanel, data);
- });
- }
-
- export function initRepoEditor() {
- const dropzoneUpload = document.querySelector<HTMLElement>('.page-content.repository.editor.upload .dropzone');
- if (dropzoneUpload) initDropzone(dropzoneUpload);
-
- for (const el of queryElems<HTMLInputElement>(document, '.js-quick-pull-choice-option')) {
- el.addEventListener('input', () => {
- if (el.value === 'commit-to-new-branch') {
- showElem('.quick-pull-branch-name');
- document.querySelector<HTMLInputElement>('.quick-pull-branch-name input').required = true;
- } else {
- hideElem('.quick-pull-branch-name');
- document.querySelector<HTMLInputElement>('.quick-pull-branch-name input').required = false;
- }
- document.querySelector('#commit-button').textContent = el.getAttribute('data-button-text');
- });
- }
-
- const filenameInput = document.querySelector<HTMLInputElement>('#file-name');
- if (!filenameInput) return;
- function joinTreePath() {
- const parts = [];
- for (const el of document.querySelectorAll('.breadcrumb span.section')) {
- const link = el.querySelector('a');
- parts.push(link ? link.textContent : el.textContent);
- }
- if (filenameInput.value) {
- parts.push(filenameInput.value);
- }
- document.querySelector<HTMLInputElement>('#tree_path').value = parts.join('/');
- }
- filenameInput.addEventListener('input', function () {
- const parts = filenameInput.value.split('/');
- const links = Array.from(document.querySelectorAll('.breadcrumb span.section'));
- const dividers = Array.from(document.querySelectorAll('.breadcrumb .breadcrumb-divider'));
- let warningDiv = document.querySelector<HTMLDivElement>('.ui.warning.message.flash-message.flash-warning.space-related');
- let containSpace = false;
- if (parts.length > 1) {
- for (let i = 0; i < parts.length; ++i) {
- const value = parts[i];
- const trimValue = value.trim();
- if (trimValue === '..') {
- // remove previous tree path
- if (links.length > 0) {
- const link = links.pop();
- const divider = dividers.pop();
- link.remove();
- divider.remove();
- }
- continue;
- }
- if (i < parts.length - 1) {
- if (trimValue.length) {
- const linkElement = createElementFromHTML(
- html`<span class="section"><a href="#">${value}</a></span>`,
- );
- const dividerElement = createElementFromHTML(
- html`<div class="breadcrumb-divider">/</div>`,
- );
- links.push(linkElement);
- dividers.push(dividerElement);
- filenameInput.before(linkElement);
- filenameInput.before(dividerElement);
- }
- } else {
- filenameInput.value = value;
- }
- this.setSelectionRange(0, 0);
- containSpace = containSpace || (trimValue !== value && trimValue !== '');
- }
- }
- containSpace = containSpace || Array.from(links).some((link) => {
- const value = link.querySelector('a').textContent;
- return value.trim() !== value;
- });
- containSpace = containSpace || parts[parts.length - 1].trim() !== parts[parts.length - 1];
- if (containSpace) {
- if (!warningDiv) {
- warningDiv = document.createElement('div');
- warningDiv.classList.add('ui', 'warning', 'message', 'flash-message', 'flash-warning', 'space-related');
- warningDiv.innerHTML = html`<p>File path contains leading or trailing whitespace.</p>`;
- // Add display 'block' because display is set to 'none' in formantic\build\semantic.css
- warningDiv.style.display = 'block';
- const inputContainer = document.querySelector('.repo-editor-header');
- inputContainer.insertAdjacentElement('beforebegin', warningDiv);
- }
- showElem(warningDiv);
- } else if (warningDiv) {
- hideElem(warningDiv);
- }
- joinTreePath();
- });
- filenameInput.addEventListener('keydown', function (e) {
- const sections = queryElems(document, '.breadcrumb span.section');
- const dividers = queryElems(document, '.breadcrumb .breadcrumb-divider');
- // Jump back to last directory once the filename is empty
- if (e.code === 'Backspace' && filenameInput.selectionStart === 0 && sections.length > 0) {
- e.preventDefault();
- const lastSection = sections[sections.length - 1];
- const lastDivider = dividers.length ? dividers[dividers.length - 1] : null;
- const value = lastSection.querySelector('a').textContent;
- filenameInput.value = value + filenameInput.value;
- this.setSelectionRange(value.length, value.length);
- lastDivider?.remove();
- lastSection.remove();
- joinTreePath();
- }
- });
-
- const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form');
-
- // on the upload page, there is no editor(textarea)
- const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
- if (!editArea) return;
-
- // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
- // to enable or disable the commit button
- const commitButton = document.querySelector<HTMLButtonElement>('#commit-button');
- const dirtyFileClass = 'dirty-file';
-
- const syncCommitButtonState = () => {
- const dirty = elForm.classList.contains(dirtyFileClass);
- commitButton.disabled = !dirty;
- };
- // Registering a custom listener for the file path and the file content
- // FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added
- applyAreYouSure(elForm, {
- silent: true,
- dirtyClass: dirtyFileClass,
- fieldSelector: ':input:not(.commit-form-wrapper :input)',
- change: syncCommitButtonState,
- });
- syncCommitButtonState(); // disable the "commit" button when no content changes
-
- initEditPreviewTab(elForm);
-
- (async () => {
- const editor = await createCodeEditor(editArea, filenameInput);
-
- // 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', async (e) => {
- // A modal which asks if an empty file should be committed
- if (!editArea.value) {
- e.preventDefault();
- if (await confirmModal({
- header: elForm.getAttribute('data-text-empty-confirm-header'),
- content: elForm.getAttribute('data-text-empty-confirm-content'),
- })) {
- ignoreAreYouSure(elForm);
- submitFormFetchAction(elForm);
- }
- }
- });
- })();
- }
-
- export function renderPreviewPanelContent(previewPanel: Element, htmlContent: string) {
- // the content is from the server, so it is safe to use innerHTML
- previewPanel.innerHTML = html`<div class="render-content markup">${htmlRaw(htmlContent)}</div>`;
- attachRefIssueContextPopup(previewPanel.querySelectorAll('p .ref-issue'));
- }
|