11import { escapeAriaLabel , escapeHtml , escapeLessThans } from "../../util/escape" ;
22import { Tab } from "../Tab" ;
33import { Model } from "../Model" ;
4- import { setPanelFocus } from "../util" ;
4+ import { getInstanceById , setPanelFocus } from "../util" ;
55import { getDockByType } from "../tabUtil" ;
66import { Constants } from "../../constants" ;
77import { getDocDisplayName , isMoveTargetAllowed , pathPosix , setNoteBook } from "../../util/pathName" ;
@@ -42,7 +42,10 @@ import {
4242 collectExpandedDocIDs ,
4343 findMovedFileTreeItem ,
4444 getFileTreeChildList ,
45+ IDocumentTabDragData ,
4546 IFileTreeMove ,
47+ insertDocumentSortPath ,
48+ parseDocumentTabDragData ,
4649 restoreMovedExpandedDocItems ,
4750 updateMovedSubtree
4851} from "../../util/fileTreeMove" ;
@@ -528,7 +531,9 @@ export class Files extends Model {
528531 /// #endif
529532 } ) ;
530533 this . element . addEventListener ( "dragover" , ( event : DragEvent & { target : HTMLElement } ) => {
531- if ( window . siyuan . config . readonly || ! window . siyuan . dragElement || event . dataTransfer . types . includes ( Constants . SIYUAN_DROP_TAB ) ) {
534+ const isDocumentTab = event . dataTransfer . types . includes ( Constants . SIYUAN_DROP_DOCUMENT_TAB ) ;
535+ if ( window . siyuan . config . readonly || ! window . siyuan . dragElement ||
536+ ( event . dataTransfer . types . includes ( Constants . SIYUAN_DROP_TAB ) && ! isDocumentTab ) ) {
532537 event . preventDefault ( ) ;
533538 return ;
534539 }
@@ -573,14 +578,19 @@ export class Files extends Model {
573578 event . preventDefault ( ) ;
574579 return ;
575580 }
576- } else if ( liElement . classList . contains ( "b3-list-item--focus" ) ) {
581+ } else if ( isDocumentTab && liElement . dataset . nodeId ===
582+ window . siyuan . dragElement . dataset . dragDocumentId ) {
583+ hideDragTip ( ) ;
584+ event . preventDefault ( ) ;
585+ return ;
586+ } else if ( ! isDocumentTab && liElement . classList . contains ( "b3-list-item--focus" ) ) {
577587 // 选中的文档不能拖拽到自己上,但允许标题拖拽到文档树的选中文档上 https://github.com/siyuan-note/siyuan/issues/6552
578588 hideDragTip ( ) ;
579589 event . preventDefault ( ) ;
580590 return ;
581591 }
582592
583- dragOverLastObj . sourceOnlyRoot = gutterType ? false : true ;
593+ dragOverLastObj . sourceOnlyRoot = gutterType || isDocumentTab ? false : true ;
584594 if ( dragOverLastObj . sourceOnlyRoot ) {
585595 const focusItems = this . element . querySelectorAll ( ".b3-list-item--focus" ) ;
586596 for ( let i = 0 ; i < focusItems . length ; i ++ ) {
@@ -665,6 +675,7 @@ export class Files extends Model {
665675 }
666676 }
667677 event . preventDefault ( ) ;
678+ event . dataTransfer . dropEffect = "move" ;
668679 } ) ;
669680 event . preventDefault ( ) ;
670681 } ) ;
@@ -686,7 +697,11 @@ export class Files extends Model {
686697 counter = 0 ;
687698 hideDragTip ( ) ;
688699 window . siyuan . dragTitle = "" ;
689- const newElement = this . element . querySelector ( ".dragover, .dragover__bottom, .dragover__top" ) ;
700+ const documentTabData = event . dataTransfer . types . includes ( Constants . SIYUAN_DROP_DOCUMENT_TAB ) ?
701+ parseDocumentTabDragData ( event . dataTransfer . getData ( Constants . SIYUAN_DROP_DOCUMENT_TAB ) ) : undefined ;
702+ const sourceTab = documentTabData ? getInstanceById ( documentTabData . tabId ) as Tab : undefined ;
703+ sourceTab ?. restoreHeadElementOrder ( ) ;
704+ const newElement = this . element . querySelector < HTMLElement > ( ".dragover, .dragover__bottom, .dragover__top" ) ;
690705 if ( ! newElement ) {
691706 return ;
692707 }
@@ -697,6 +712,13 @@ export class Files extends Model {
697712 const oldScrollTop = this . element . scrollTop ;
698713 const toURL = newUlElement . getAttribute ( "data-url" ) ;
699714 const toPath = newElement . getAttribute ( "data-path" ) ;
715+ if ( documentTabData ) {
716+ event . preventDefault ( ) ;
717+ event . stopPropagation ( ) ;
718+ window . siyuan . dragElement = undefined ;
719+ await this . dropDocumentTab ( documentTabData , newElement , newUlElement , oldScrollTop ) ;
720+ return ;
721+ }
700722 let gutterType = "" ;
701723 for ( const item of event . dataTransfer . items ) {
702724 if ( item . type . startsWith ( Constants . SIYUAN_DROP_GUTTER ) ) {
@@ -899,6 +921,104 @@ export class Files extends Model {
899921 this . init ( ) ;
900922 }
901923
924+ private async dropDocumentTab (
925+ documentTabData : IDocumentTabDragData ,
926+ targetElement : HTMLElement ,
927+ notebookElement : HTMLElement ,
928+ oldScrollTop : number
929+ ) {
930+ const moveAsChild = targetElement . classList . contains ( "dragover" ) ;
931+ const insertAfter = targetElement . classList . contains ( "dragover__bottom" ) ;
932+ const insertBefore = targetElement . classList . contains ( "dragover__top" ) ;
933+ const targetNotebook = notebookElement . getAttribute ( "data-url" ) ;
934+ const targetPath = targetElement . getAttribute ( "data-path" ) ;
935+ targetElement . classList . remove ( "dragover" , "dragover__bottom" , "dragover__top" ) ;
936+ if ( ! targetNotebook || ! targetPath || ( ! moveAsChild && ! insertAfter && ! insertBefore ) ) {
937+ return ;
938+ }
939+
940+ const pathResponse = await fetchSyncPost ( "/api/filetree/getPathByID" , { id : documentTabData . rootId } ) ;
941+ if ( pathResponse . code !== 0 || ! pathResponse . data ?. path || ! pathResponse . data ?. notebook ) {
942+ return ;
943+ }
944+ const sourcePath = pathResponse . data . path as string ;
945+ const sourceNotebook = pathResponse . data . notebook as string ;
946+ if ( ! isMoveTargetAllowed ( [ sourceNotebook ] , targetNotebook ) ) {
947+ showMessage ( window . siyuan . languages . _kernel [ 313 ] ) ;
948+ return ;
949+ }
950+ if ( sourceNotebook === targetNotebook ) {
951+ const sourceDirectory = sourcePath . endsWith ( ".sy" ) ? sourcePath . slice ( 0 , - 3 ) : sourcePath ;
952+ if ( targetPath === sourcePath || targetPath . startsWith ( sourceDirectory + "/" ) ) {
953+ return ;
954+ }
955+ }
956+
957+ if ( moveAsChild ) {
958+ const sourceParentDirectory = pathPosix ( ) . dirname ( sourcePath ) ;
959+ const sourceParentPath = sourceParentDirectory === "/" ? "/" : sourceParentDirectory + ".sy" ;
960+ if ( sourceNotebook === targetNotebook && sourceParentPath === targetPath ) {
961+ return ;
962+ }
963+ await fetchSyncPost ( "/api/filetree/moveDocs" , {
964+ toNotebook : targetNotebook ,
965+ fromPaths : [ sourcePath ] ,
966+ toPath : targetPath ,
967+ } ) ;
968+ return ;
969+ }
970+
971+ const notebookSort = notebookElement . getAttribute ( "data-sortmode" ) ;
972+ if ( notebookSort !== "6" && ! ( window . siyuan . config . fileTree . sort === 6 && notebookSort === "15" ) ) {
973+ return ;
974+ }
975+ const targetDirectory = pathPosix ( ) . dirname ( targetPath ) ;
976+ const newPath = pathPosix ( ) . join ( targetDirectory , documentTabData . rootId + ".sy" ) ;
977+ const siblingPaths : string [ ] = [ ] ;
978+ Array . from ( targetElement . parentElement . children ) . forEach ( ( item ) => {
979+ const path = item . getAttribute ( "data-path" ) ;
980+ if ( item . tagName === "LI" && path ) {
981+ siblingPaths . push ( path ) ;
982+ }
983+ } ) ;
984+ const sortedPaths = insertDocumentSortPath (
985+ siblingPaths ,
986+ documentTabData . rootId ,
987+ newPath ,
988+ targetPath ,
989+ insertAfter
990+ ) ;
991+ if ( ! sortedPaths || ( sortedPaths . length === siblingPaths . length &&
992+ sortedPaths . every ( ( path , index ) => path === siblingPaths [ index ] ) ) ) {
993+ return ;
994+ }
995+ const targetParentPath = targetDirectory === "/" ? "/" : targetDirectory + ".sy" ;
996+ const moveResponse = await fetchSyncPost ( "/api/filetree/moveDocs" , {
997+ toNotebook : targetNotebook ,
998+ fromPaths : [ sourcePath ] ,
999+ toPath : targetParentPath ,
1000+ callback : Constants . CB_MOVE_NOLIST ,
1001+ } ) ;
1002+ if ( moveResponse . code !== 0 ) {
1003+ return ;
1004+ }
1005+ const sortResponse = await fetchSyncPost ( "/api/filetree/changeSort" , {
1006+ paths : sortedPaths ,
1007+ notebook : targetNotebook ,
1008+ } ) ;
1009+ if ( sortResponse . code !== 0 ) {
1010+ return ;
1011+ }
1012+ const listResponse = await fetchSyncPost ( "/api/filetree/listDocsByPath" , {
1013+ notebook : targetNotebook ,
1014+ path : targetParentPath ,
1015+ app : Constants . SIYUAN_APPID ,
1016+ } ) ;
1017+ if ( listResponse . code === 0 && listResponse . data ?. files ?. length > 0 ) {
1018+ this . onLsHTML ( listResponse . data , oldScrollTop ) ;
1019+ }
1020+ }
1021+
9021022 private handleMsgCallback ( data : IWebSocketData ) {
9031023 if ( data ) {
9041024 switch ( data . cmd ) {
0 commit comments