import { EditorView as ProseEditorView , EditorView } from "prosemirror-view" ; import { Mark , Slice , Node as ProseNode } from "prosemirror-model" ; import { chainCommands , Keymap , joinUp , joinDown , lift , selectParentNode } from "prosemirror-commands" ; import { EditorState as ProseEditorState , Transaction , Plugin as ProsePlugin } from "prosemirror-state" ; import { history , undo , redo } from "prosemirror-history" ; import { gapCursor } from "prosemirror-gapcursor" ; import { undoInputRule } from "prosemirror-inputrules" ; import { IPossiblyUntitledFile } from "@common/files" ; import { Editor } from "./editor" ; import * as Md from "@common/markdown/markdown-ast" ; import { render } from "solid-js/web" ; import { createEffect , createSignal } from "solid-js" ; import { mathPlugin , mathBackspaceCmd , mathSelectPlugin } from "@benrbray/prosemirror-math" ; import { MainIpcHandlers } from "@main/MainIPC" ; import { YamlEditor } from "../ui/yamlEditor" ; import { SetDocAttrStep } from "@common/prosemirror/steps" ; import { shallowEqual } from "@common/util/equal" ; import { makeSuggestionPlugin , SuggestionPopup } from "@renderer/ui/suggestions" ; import { moveSelectionDown , moveSelectionUp } from "@common/prosemirror/commands/moveSelection" ; import { insertTab } from "@common/prosemirror/commands/insertTab" ; import { EditorConfig } from "@common/extensions/editor-config" ; import { NwtExtension } from "@common/extensions/extension" ; import { citationPlugin } from "@main/plugins/crossref-plugin" ; import { BlockQuoteExtension , HeadingExtension , HorizontalRuleExtension , CodeBlockExtension , InlineMathExtension , BlockMathExtension , OrderedListExtension , UnorderedListExtension , ListItemExtension , ContainerDirectiveExtension , ImageExtension , HardBreakExtension , RootExtension , ParagraphExtension , CitationExtension } from "@common/extensions/node-extensions" ; import { BoldExtension , ItalicExtension , LinkExtension , CodeExtension , WikilinkExtension , } from "@common/extensions/mark-extensions" ; import { WindowAfterPreload } from "@renderer/preload_types" ; import { ProseSchema } from "@common/types" ; import { makeDefaultMarkdownExtensions } from "@common/doctypes/markdown-doc" ; declare let window ; todo const mac = typeof navigator != "undefined" ? /Mac/ . test ( navigator . platform ) : false ; export class MarkdownEditor < S extends ProseSchema = ProseSchema > extends Editor < ProseEditorState < S > > { _proseEditorView : ProseEditorView | null ; _initialized : boolean ; _config : EditorConfig ; todo _paragraphExt : ParagraphExtension ; _citationExt : CitationExtension ; _imageExt : ImageExtension ; _metaElt : HTMLElement ; popup : SuggestionPopup | null ; constructor ( file , editorElt , mainProxy ) { super ( file , editorElt , mainProxy ) ; this . _initialized = false ; this . _proseEditorView = null ; this . _metaElt = document . createElement ( "div" ) ; this . _metaElt . setAttribute ( "id" , "meta-editor" ) ; this . _metaElt . setAttribute ( "class" , "meta-editor" ) ; this . _editorElt . appendChild ( this . _metaElt ) ; this . popup = null ; let extensions = [ new RootExtension ( ) , ( this . _paragraphExt = new ParagraphExtension ( ) ) , new BlockQuoteExtension ( ) , new HeadingExtension ( this . _paragraphExt ) , new HorizontalRuleExtension ( ) , new CodeBlockExtension ( ) , new OrderedListExtension ( ) , new UnorderedListExtension ( ) , new ListItemExtension ( ) , ( this . _imageExt = new ImageExtension ( ) ) , new HardBreakExtension ( ) , new InlineMathExtension ( ) , new BlockMathExtension ( ) , new ContainerDirectiveExtension ( ) , new BoldExtension ( ) , new ItalicExtension ( ) , new LinkExtension ( ) , new CodeExtension ( ) , new WikilinkExtension ( ) , ( this . _citationExt = new CitationExtension ( ) ) ] ; let plugins = [ mathSelectPlugin , mathPlugin , citationPlugin ( { handleCitationOpen : async ( tag ) : Promise < void > => { todo let directoryHint = this . _currentFile ?. dirPath ; if ( tag ) { return this . _mainProxy . navigation . navigateToTag ( { tag , create : true , directoryHint } ) ; } } , renderCitation : async ( contentStr , attrs ) : Promise < string | null > => { console . log ( `renderCitation ::` , contentStr ) ; let citeSyntax = attrs . pandocSyntax ? `[${ contentStr }]` : `@[${ contentStr }]` ; let root = this . _config . parseAST ( citeSyntax ) as Md . Root ; console . log ( "parsed citation" , root ) ; if ( ! root ) { return null ; } if ( root . type !== "root" || root . children . length !== 1 ) { return null ; } let par = root . children [ 0 ] ; if ( par . type !== "paragraph" || par . children . length !== 1 ) { return null ; } let cite = par . children [ 0 ] ; if ( cite . type !== "cite" ) { return null ; } for ( let item of cite . data . citeItems ) { console . log ( "found key" , item ) ; } let key = cite . data . citeItems [ 0 ] . key ; let hash = await this . _mainProxy . tag . getHashForTag ( { tag : key , create : false } ) ; if ( hash === null ) { console . warn ( `renderCitation :: tag "${ key }" does not correspond to a hash` ) ; return null ; } if ( hash === undefined ) { console . error ( `renderCitation :: no response from main process when querying for tag "${ key }"` ) ; return null ; } let meta = await this . _mainProxy . metadata . getMetadataForHash ( hash ) ; if ( meta === null ) { console . warn ( `renderCitation :: no metadata found for hash ${ hash }` ) ; return null ; } console . log ( meta ) ; let author = meta [ "author" ] ; let authors = meta [ "authors" ] ; let date = meta [ "date" ] ; let year = meta [ "year" ] ; todo if ( ! author && Array . isArray ( authors ) ) { author = authors [ 0 ] ; } if ( ! date && ! Array . isArray ( year ) ) { date = year ; } if ( Array . isArray ( date ) ) { date = date [ 0 ] ; } if ( Array . isArray ( author ) ) { author = author [ 0 ] ; } let parsedDate = new Date ( date ) ; if ( isNaN ( parsedDate . valueOf ( ) ) ) { console . warn ( `renderCitation :: invalid date ${ date }` ) ; return null ; } if ( ! author || ! date ) { console . warn ( `renderCitation :: not enough fields` ) ; return null ; } let names = author . split ( /\s+/ ) ; let lastName = names [ names . length - 1 ] ; return `${ lastName } ${ parsedDate . getFullYear ( ) }` ; } } ) , makeSuggestionPlugin ( this ) , history ( ) , gapCursor ( ) ] ; let keymap = { "Tab" : insertTab , "Backspace" : chainCommands ( mathBackspaceCmd , undoInputRule ) , "Ctrl-s" : ( ) => { this . saveCurrentFile ( false ) ; return true ; } , "Mod-z" : undo , "Shift-Mod-z" : redo , ... ( ! mac && { "Mod-y" : redo } ) , "Alt-ArrowUp" : chainCommands ( moveSelectionUp ( ) , joinUp ) , "Alt-ArrowDown" : chainCommands ( moveSelectionDown ( ) , joinDown ) , "Mod-BracketLeft" : lift , "Escape" : selectParentNode } ; this . _config = new EditorConfig ( extensions , plugins , keymap ) ; } init ( ) { if ( this . _initialized ) { return ; } this . initProseEditor ( ) ; this . initYamlEditor ( ) ; this . initPopup ( ) ; this . _initialized = true ; } initYamlEditor ( ) { if ( this . _initialized ) { return ; } if ( ! this . _proseEditorView ) { throw new Error ( "cannot initialize YAML editor before ProseMirror" ) ; } let state = this . _proseEditorView . state ; let [ yamlMeta , setYamlMeta ] = createSignal ( { data : state . doc . attrs [ 'yamlMeta' ] } ) ; const Editor = ( ) => { createEffect ( ( ) => { let data = yamlMeta ( ) . data ; let proseView = this . _proseEditorView ; if ( ! proseView ) { return ; } todo if ( shallowEqual ( data , proseView . state . doc . attrs [ 'yamlMeta' ] ) ) { console . log ( "editor :: no metadata change detected" ) ; return ; } proseView . dispatch ( proseView . state . tr . step ( new SetDocAttrStep ( "yamlMeta" , data ) ) ) ; } ) ; return ( < YamlEditor yamlMeta = { yamlMeta ( ) . data } setYamlMeta = { setYamlMeta } / > ) ; } ; render ( Editor , this . _metaElt ) ; } initPopup ( ) { if ( ! this . _proseEditorView ) { return ; } if ( ! this . _editorElt ) { return ; } this . popup = new SuggestionPopup ( this . _proseEditorView , this . _editorElt , this . _mainProxy ) ; } initProseEditor ( ) { if ( this . _initialized ) { return ; } let state ; if ( this . _currentFile && this . _currentFile . contents ) { state = this . parseContents ( this . _currentFile . contents ) ; } else { state = ProseEditorState . create ( this . _config ) ; } this . _proseEditorView = new ProseEditorView ( this . _editorElt , { state : state , nodeViews : { } , dispatchTransaction : ( tr ) : void => { if ( tr . docChanged ) { this . handleDocChanged ( ) ; } let proseView = ( this . _proseEditorView as EditorView ) ; todo if ( tr . steps . find ( ( value ) => ( value instanceof SetDocAttrStep ) ) ) { } console . log ( "selection :: " , tr . selection . from , tr . selection . to ) ; proseView . updateState ( proseView . state . apply ( tr ) ) ; } , handleClick : ( view , pos , event ) => { let node = view . state . doc . nodeAt ( pos ) ; if ( ! node ) { return false ; } let mark ; if ( event . ctrlKey && node . isText ) { let markTypes = [ "wikilink" , "citation" , "tag" ] ; if ( mark = node . marks . find ( ( mark ) => markTypes . includes ( mark . type . name ) ) ) { let tag = node . text ; let directoryHint = this . _currentFile ?. dirPath ; if ( tag ) { this . _mainProxy . navigation . navigateToTag ( { tag , create : true , directoryHint } ) ; } return true ; } else if ( mark = node . marks . find ( ( mark ) => mark . type . name == "link" ) ) { let url = mark . attrs . href ; if ( url ) { this . _mainProxy . shell . requestExternalLinkOpen ( url ) ; } return true ; } } return false ; } , handlePaste : ( view ) => { todo let clipboardImageURI = window . clipboardApi . getClipboardImageDataURI ( ) ; if ( clipboardImageURI == null ) { let imgNode = this . _imageExt . nodeType . createAndFill ( { src : clipboardImageURI } ) ; if ( imgNode ) { let { $from } = view . state . selection ; let tr = view . state . tr . deleteSelection ( ) . insert ( $from . pos , imgNode ) ; view . dispatch ( tr ) ; return true ; } } return false ; } } ) ; } destroy ( ) { this . _proseEditorView ?. destroy ( ) ; this . _proseEditorView = null ; this . _metaElt . remove ( ) ; this . popup ?. dispose ( ) ; this . _initialized = false ; } /**
     * Serialize the contents of this editor as a string.
     */ serializeContents ( ) { if ( ! this . _proseEditorView ) { return "" ; } let serialized = this . _config . serialize ( this . _proseEditorView . state . doc ) ; if ( ! serialized ) { throw new Error ( "serialization error!" ) ; } return serialized ; } /**
     * Use this editor's configuration to convert a Markdown string to a ProseMirror document.
     */ parseContents ( markdown ) { let proseDoc = this . _config . parse ( markdown ) ; if ( ! proseDoc ) { throw new Error ( "parse error!" ) ; } return ProseEditorState . create ( { doc : proseDoc , plugins : this . _config . plugins , schema : this . _config . schema , } ) ; } /**
     * Replaces the contents of the ProseMirror editor.
     */ setContents ( contents ) { if ( ! this . _proseEditorView ) { console . warn ( "editor-markdown :: setContents :: no editor!" ) ; return ; } this . _proseEditorView . updateState ( contents ) ; } }