2021-11-18 02:08:25 +08:00
|
|
|
import {htmlEscape} from 'escape-goat';
|
2024-07-07 23:32:30 +08:00
|
|
|
import {createCodeEditor} from './codeeditor.ts';
|
2024-07-27 22:44:41 +08:00
|
|
|
import {hideElem, queryElems, showElem, createElementFromHTML} from '../utils/dom.ts';
|
2024-07-07 23:32:30 +08:00
|
|
|
import {initMarkupContent} from '../markup/content.ts';
|
|
|
|
import {attachRefIssueContextPopup} from './contextpopup.ts';
|
|
|
|
import {POST} from '../modules/fetch.ts';
|
|
|
|
import {initDropzone} from './dropzone.ts';
|
2024-12-04 17:26:54 +08:00
|
|
|
import {confirmModal} from './comp/ConfirmModal.ts';
|
|
|
|
import {applyAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
|
|
|
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
2021-10-17 01:28:04 +08:00
|
|
|
|
2024-12-04 17:26:54 +08:00
|
|
|
function initEditPreviewTab(elForm: HTMLFormElement) {
|
|
|
|
const elTabMenu = elForm.querySelector('.repo-editor-menu');
|
|
|
|
fomanticQuery(elTabMenu.querySelectorAll('.item')).tab();
|
2024-03-07 15:28:33 +08:00
|
|
|
|
2024-12-04 17:26:54 +08:00
|
|
|
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);
|
|
|
|
});
|
2021-10-17 01:28:04 +08:00
|
|
|
}
|
|
|
|
|
2021-11-09 17:27:25 +08:00
|
|
|
export function initRepoEditor() {
|
2024-06-27 01:01:20 +08:00
|
|
|
const dropzoneUpload = document.querySelector('.page-content.repository.editor.upload .dropzone');
|
|
|
|
if (dropzoneUpload) initDropzone(dropzoneUpload);
|
|
|
|
|
2024-11-08 14:04:24 +08:00
|
|
|
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
|
2024-06-27 01:01:20 +08:00
|
|
|
if (!editArea) return;
|
2021-10-17 01:28:04 +08:00
|
|
|
|
2024-11-08 14:04:24 +08:00
|
|
|
for (const el of queryElems<HTMLInputElement>(document, '.js-quick-pull-choice-option')) {
|
2024-06-10 18:12:31 +08:00
|
|
|
el.addEventListener('input', () => {
|
|
|
|
if (el.value === 'commit-to-new-branch') {
|
|
|
|
showElem('.quick-pull-branch-name');
|
2024-11-08 14:04:24 +08:00
|
|
|
document.querySelector<HTMLInputElement>('.quick-pull-branch-name input').required = true;
|
2023-03-04 06:28:20 +08:00
|
|
|
} else {
|
2024-06-10 18:12:31 +08:00
|
|
|
hideElem('.quick-pull-branch-name');
|
2024-11-08 14:04:24 +08:00
|
|
|
document.querySelector<HTMLInputElement>('.quick-pull-branch-name input').required = false;
|
2023-03-04 06:28:20 +08:00
|
|
|
}
|
2024-06-10 18:12:31 +08:00
|
|
|
document.querySelector('#commit-button').textContent = el.getAttribute('data-button-text');
|
2023-03-04 06:28:20 +08:00
|
|
|
});
|
2024-06-10 18:12:31 +08:00
|
|
|
}
|
2021-10-17 01:28:04 +08:00
|
|
|
|
2024-07-27 22:44:41 +08:00
|
|
|
const filenameInput = document.querySelector<HTMLInputElement>('#file-name');
|
2024-06-10 18:12:31 +08:00
|
|
|
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);
|
|
|
|
}
|
2024-11-08 14:04:24 +08:00
|
|
|
document.querySelector<HTMLInputElement>('#tree_path').value = parts.join('/');
|
2024-06-10 18:12:31 +08:00
|
|
|
}
|
|
|
|
filenameInput.addEventListener('input', function () {
|
|
|
|
const parts = filenameInput.value.split('/');
|
2024-09-25 03:06:52 +08:00
|
|
|
const links = Array.from(document.querySelectorAll('.breadcrumb span.section'));
|
|
|
|
const dividers = Array.from(document.querySelectorAll('.breadcrumb .breadcrumb-divider'));
|
2024-11-08 14:04:24 +08:00
|
|
|
let warningDiv = document.querySelector<HTMLDivElement>('.ui.warning.message.flash-message.flash-warning.space-related');
|
2024-09-25 03:06:52 +08:00
|
|
|
let containSpace = false;
|
2023-03-04 06:28:20 +08:00
|
|
|
if (parts.length > 1) {
|
2021-10-17 01:28:04 +08:00
|
|
|
for (let i = 0; i < parts.length; ++i) {
|
2023-03-04 06:28:20 +08:00
|
|
|
const value = parts[i];
|
2024-09-25 03:06:52 +08:00
|
|
|
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;
|
|
|
|
}
|
2021-10-17 01:28:04 +08:00
|
|
|
if (i < parts.length - 1) {
|
2024-09-25 03:06:52 +08:00
|
|
|
if (trimValue.length) {
|
|
|
|
const linkElement = createElementFromHTML(
|
2024-07-27 22:44:41 +08:00
|
|
|
`<span class="section"><a href="#">${htmlEscape(value)}</a></span>`,
|
2024-09-25 03:06:52 +08:00
|
|
|
);
|
|
|
|
const dividerElement = createElementFromHTML(
|
2024-07-27 22:44:41 +08:00
|
|
|
`<div class="breadcrumb-divider">/</div>`,
|
2024-09-25 03:06:52 +08:00
|
|
|
);
|
|
|
|
links.push(linkElement);
|
|
|
|
dividers.push(dividerElement);
|
|
|
|
filenameInput.before(linkElement);
|
|
|
|
filenameInput.before(dividerElement);
|
2021-10-17 01:28:04 +08:00
|
|
|
}
|
|
|
|
} else {
|
2024-06-10 18:12:31 +08:00
|
|
|
filenameInput.value = value;
|
2021-10-17 01:28:04 +08:00
|
|
|
}
|
2023-03-04 06:28:20 +08:00
|
|
|
this.setSelectionRange(0, 0);
|
2024-11-08 14:04:24 +08:00
|
|
|
containSpace = containSpace || (trimValue !== value && trimValue !== '');
|
2024-09-25 03:06:52 +08:00
|
|
|
}
|
|
|
|
}
|
2024-11-08 14:04:24 +08:00
|
|
|
containSpace = containSpace || Array.from(links).some((link) => {
|
2024-09-25 03:06:52 +08:00
|
|
|
const value = link.querySelector('a').textContent;
|
|
|
|
return value.trim() !== value;
|
|
|
|
});
|
2024-11-08 14:04:24 +08:00
|
|
|
containSpace = containSpace || parts[parts.length - 1].trim() !== parts[parts.length - 1];
|
2024-09-25 03:06:52 +08:00
|
|
|
if (containSpace) {
|
|
|
|
if (!warningDiv) {
|
|
|
|
warningDiv = document.createElement('div');
|
|
|
|
warningDiv.classList.add('ui', 'warning', 'message', 'flash-message', 'flash-warning', 'space-related');
|
|
|
|
warningDiv.innerHTML = '<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);
|
2021-10-17 01:28:04 +08:00
|
|
|
}
|
2024-09-25 03:06:52 +08:00
|
|
|
showElem(warningDiv);
|
|
|
|
} else if (warningDiv) {
|
|
|
|
hideElem(warningDiv);
|
2021-10-17 01:28:04 +08:00
|
|
|
}
|
2024-06-10 18:12:31 +08:00
|
|
|
joinTreePath();
|
2023-03-04 06:28:20 +08:00
|
|
|
});
|
2024-06-10 18:12:31 +08:00
|
|
|
filenameInput.addEventListener('keydown', function (e) {
|
2024-11-08 14:04:24 +08:00
|
|
|
const sections = queryElems(document, '.breadcrumb span.section');
|
|
|
|
const dividers = queryElems(document, '.breadcrumb .breadcrumb-divider');
|
2023-03-04 06:28:20 +08:00
|
|
|
// Jump back to last directory once the filename is empty
|
2024-06-10 18:12:31 +08:00
|
|
|
if (e.code === 'Backspace' && filenameInput.selectionStart === 0 && sections.length > 0) {
|
2023-03-04 06:28:20 +08:00
|
|
|
e.preventDefault();
|
2024-06-10 18:12:31 +08:00
|
|
|
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;
|
2023-03-04 06:28:20 +08:00
|
|
|
this.setSelectionRange(value.length, value.length);
|
2024-06-10 18:12:31 +08:00
|
|
|
lastDivider?.remove();
|
|
|
|
lastSection.remove();
|
|
|
|
joinTreePath();
|
2023-03-04 06:28:20 +08:00
|
|
|
}
|
|
|
|
});
|
2021-10-17 01:28:04 +08:00
|
|
|
|
2024-12-04 17:26:54 +08:00
|
|
|
const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form');
|
|
|
|
initEditPreviewTab(elForm);
|
2021-10-17 01:28:04 +08:00
|
|
|
|
2021-11-09 17:27:25 +08:00
|
|
|
(async () => {
|
2024-06-27 01:01:20 +08:00
|
|
|
const editor = await createCodeEditor(editArea, filenameInput);
|
2021-10-17 01:28:04 +08:00
|
|
|
|
2021-11-09 17:27:25 +08:00
|
|
|
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
|
|
|
|
// to enable or disable the commit button
|
2024-11-08 14:04:24 +08:00
|
|
|
const commitButton = document.querySelector<HTMLButtonElement>('#commit-button');
|
2021-11-09 17:27:25 +08:00
|
|
|
const dirtyFileClass = 'dirty-file';
|
2021-10-17 01:28:04 +08:00
|
|
|
|
2021-11-09 17:27:25 +08:00
|
|
|
// Disabling the button at the start
|
2024-12-04 17:26:54 +08:00
|
|
|
if (document.querySelector<HTMLInputElement>('input[name="page_has_posted"]').value !== 'true') {
|
2024-03-16 23:08:10 +08:00
|
|
|
commitButton.disabled = true;
|
2021-10-17 01:28:04 +08:00
|
|
|
}
|
|
|
|
|
2021-11-09 17:27:25 +08:00
|
|
|
// Registering a custom listener for the file path and the file content
|
2024-12-04 17:26:54 +08:00
|
|
|
// FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added
|
|
|
|
applyAreYouSure(elForm, {
|
2021-11-09 17:27:25 +08:00
|
|
|
silent: true,
|
|
|
|
dirtyClass: dirtyFileClass,
|
|
|
|
fieldSelector: ':input:not(.commit-form-wrapper :input)',
|
2024-03-30 03:24:17 +08:00
|
|
|
change($form) {
|
|
|
|
const dirty = $form[0]?.classList.contains(dirtyFileClass);
|
2024-03-16 23:08:10 +08:00
|
|
|
commitButton.disabled = !dirty;
|
2021-11-09 17:27:25 +08:00
|
|
|
},
|
|
|
|
});
|
2021-10-17 01:28:04 +08:00
|
|
|
|
2021-11-09 17:27:25 +08:00
|
|
|
// 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);
|
2021-10-17 01:28:04 +08:00
|
|
|
}
|
2021-11-09 17:27:25 +08:00
|
|
|
|
2024-12-04 17:26:54 +08:00
|
|
|
commitButton?.addEventListener('click', async (e) => {
|
2021-11-09 17:27:25 +08:00
|
|
|
// A modal which asks if an empty file should be committed
|
2024-06-27 01:01:20 +08:00
|
|
|
if (!editArea.value) {
|
2024-03-16 23:08:10 +08:00
|
|
|
e.preventDefault();
|
2024-12-04 17:26:54 +08:00
|
|
|
if (await confirmModal({
|
|
|
|
header: elForm.getAttribute('data-text-empty-confirm-header'),
|
|
|
|
content: elForm.getAttribute('data-text-empty-confirm-content'),
|
|
|
|
})) {
|
|
|
|
elForm.classList.remove('dirty');
|
|
|
|
elForm.submit();
|
|
|
|
}
|
2021-11-09 17:27:25 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
2021-10-17 01:28:04 +08:00
|
|
|
}
|
2023-04-12 11:03:23 +08:00
|
|
|
|
2024-12-04 10:11:34 +08:00
|
|
|
export function renderPreviewPanelContent(previewPanel: Element, content: string) {
|
|
|
|
previewPanel.innerHTML = content;
|
2023-04-12 11:03:23 +08:00
|
|
|
initMarkupContent();
|
2024-12-04 10:11:34 +08:00
|
|
|
attachRefIssueContextPopup(previewPanel.querySelectorAll('p .ref-issue'));
|
2023-04-12 11:03:23 +08:00
|
|
|
}
|