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