2022-01-28 13:00:11 -08:00
import $ from 'jquery' ;
2021-11-17 18:08:25 +00:00
import { htmlEscape } from 'escape-goat' ;
2024-07-07 17:32:30 +02:00
import { createCodeEditor } from './codeeditor.ts' ;
2024-07-27 16:44:41 +02:00
import { hideElem , queryElems , showElem , createElementFromHTML } from '../utils/dom.ts' ;
2024-07-07 17:32:30 +02:00
import { initMarkupContent } from '../markup/content.ts' ;
import { attachRefIssueContextPopup } from './contextpopup.ts' ;
import { POST } from '../modules/fetch.ts' ;
import { initDropzone } from './dropzone.ts' ;
2021-10-17 01:28:04 +08:00
function initEditPreviewTab ( $form ) {
2024-04-29 22:53:15 +02:00
const $tabMenu = $form . find ( '.repo-editor-menu' ) ;
2021-10-17 01:28:04 +08:00
$tabMenu . find ( '.item' ) . tab ( ) ;
2024-04-29 22:53:15 +02:00
const $previewTab = $tabMenu . find ( 'a[data-tab="preview"]' ) ;
2021-10-17 01:28:04 +08:00
if ( $previewTab . length ) {
2024-03-07 09:28:33 +02:00
$previewTab . on ( 'click' , async function ( ) {
2021-10-17 01:28:04 +08:00
const $this = $ ( this ) ;
let context = ` ${ $this . data ( 'context' ) } / ` ;
2023-03-24 07:12:23 +01:00
const mode = $this . data ( 'markup-mode' ) || 'comment' ;
2024-03-16 14:22:16 +02:00
const $treePathEl = $form . find ( 'input#tree_path' ) ;
if ( $treePathEl . length > 0 ) {
context += $treePathEl . val ( ) ;
2021-10-17 01:28:04 +08:00
}
context = context . substring ( 0 , context . lastIndexOf ( '/' ) ) ;
2024-03-07 09:28:33 +02:00
const formData = new FormData ( ) ;
formData . append ( 'mode' , mode ) ;
formData . append ( 'context' , context ) ;
2024-04-29 22:53:15 +02:00
formData . append ( 'text' , $form . find ( '.tab[data-tab="write"] textarea' ) . val ( ) ) ;
2024-03-16 14:22:16 +02:00
formData . append ( 'file_path' , $treePathEl . val ( ) ) ;
2024-03-07 09:28:33 +02:00
try {
const response = await POST ( $this . data ( 'url' ) , { data : formData } ) ;
const data = await response . text ( ) ;
2024-04-29 22:53:15 +02:00
const $previewPanel = $form . find ( '.tab[data-tab="preview"]' ) ;
if ( $previewPanel . length ) {
renderPreviewPanelContent ( $previewPanel , data ) ;
}
2024-03-07 09:28:33 +02:00
} catch ( error ) {
console . error ( 'Error:' , error ) ;
}
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 ) ;
const editArea = document . querySelector ( '.page-content.repository.editor textarea#edit_area' ) ;
if ( ! editArea ) return ;
2021-10-17 01:28:04 +08:00
2024-06-10 12:12:31 +02:00
for ( const el of queryElems ( '.js-quick-pull-choice-option' ) ) {
el . addEventListener ( 'input' , ( ) = > {
if ( el . value === 'commit-to-new-branch' ) {
showElem ( '.quick-pull-branch-name' ) ;
document . querySelector ( '.quick-pull-branch-name input' ) . required = true ;
2023-03-04 06:28:20 +08:00
} else {
2024-06-10 12:12:31 +02:00
hideElem ( '.quick-pull-branch-name' ) ;
document . querySelector ( '.quick-pull-branch-name input' ) . required = false ;
2023-03-04 06:28:20 +08:00
}
2024-06-10 12:12:31 +02:00
document . querySelector ( '#commit-button' ) . textContent = el . getAttribute ( 'data-button-text' ) ;
2023-03-04 06:28:20 +08:00
} ) ;
2024-06-10 12:12:31 +02:00
}
2021-10-17 01:28:04 +08:00
2024-07-27 16:44:41 +02:00
const filenameInput = document . querySelector < HTMLInputElement > ( '#file-name' ) ;
2024-06-10 12:12:31 +02: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 ) ;
}
document . querySelector ( '#tree_path' ) . value = parts . join ( '/' ) ;
}
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' ) ) ;
let warningDiv = document . querySelector ( '.ui.warning.message.flash-message.flash-warning.space-related' ) ;
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 16:44:41 +02:00
` <span class="section"><a href="#"> ${ htmlEscape ( value ) } </a></span> ` ,
2024-09-25 03:06:52 +08:00
) ;
const dividerElement = createElementFromHTML (
2024-07-27 16:44:41 +02: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 12:12:31 +02: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-09-25 03:06:52 +08:00
containSpace |= ( trimValue !== value && trimValue !== '' ) ;
}
}
containSpace |= Array . from ( links ) . some ( ( link ) = > {
const value = link . querySelector ( 'a' ) . textContent ;
return value . trim ( ) !== value ;
} ) ;
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 = '<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 12:12:31 +02:00
joinTreePath ( ) ;
2023-03-04 06:28:20 +08:00
} ) ;
2024-06-10 12:12:31 +02:00
filenameInput . addEventListener ( 'keydown' , function ( e ) {
const sections = queryElems ( '.breadcrumb span.section' ) ;
const dividers = queryElems ( '.breadcrumb .breadcrumb-divider' ) ;
2023-03-04 06:28:20 +08:00
// Jump back to last directory once the filename is empty
2024-06-10 12:12:31 +02:00
if ( e . code === 'Backspace' && filenameInput . selectionStart === 0 && sections . length > 0 ) {
2023-03-04 06:28:20 +08:00
e . preventDefault ( ) ;
2024-06-10 12:12:31 +02: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 12:12:31 +02:00
lastDivider ? . remove ( ) ;
lastSection . remove ( ) ;
joinTreePath ( ) ;
2023-03-04 06:28:20 +08:00
}
} ) ;
2021-10-17 01:28:04 +08:00
2024-06-10 12:12:31 +02:00
const $form = $ ( '.repository.editor .edit.form' ) ;
initEditPreviewTab ( $form ) ;
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-06-10 22:49:33 +02:00
const commitButton = document . querySelector ( '#commit-button' ) ;
2021-11-09 17:27:25 +08:00
const $editForm = $ ( '.ui.edit.form' ) ;
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
if ( $ ( 'input[name="page_has_posted"]' ) . val ( ) !== 'true' ) {
2024-03-16 16:08:10 +01: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
$editForm . areYouSure ( {
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 16:08:10 +01: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-03-16 16:08:10 +01:00
commitButton ? . addEventListener ( 'click' , ( 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 ) {
2021-11-09 17:27:25 +08:00
$ ( '#edit-empty-content-modal' ) . modal ( {
onApprove() {
$ ( '.edit.form' ) . trigger ( 'submit' ) ;
} ,
} ) . modal ( 'show' ) ;
2024-03-16 16:08:10 +01:00
e . preventDefault ( ) ;
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-04-29 22:53:15 +02:00
export function renderPreviewPanelContent ( $previewPanel , data ) {
$previewPanel . html ( data ) ;
2023-04-12 11:03:23 +08:00
initMarkupContent ( ) ;
2024-04-29 22:53:15 +02:00
const $refIssues = $previewPanel . find ( 'p .ref-issue' ) ;
2024-03-16 14:22:16 +02:00
attachRefIssueContextPopup ( $refIssues ) ;
2023-04-12 11:03:23 +08:00
}