import { Node as ProseNode , NodeSpec , DOMOutputSpec } from "prosemirror-model" ; import { wrapInList , splitListItem , liftListItem , sinkListItem } from "prosemirror-schema-list" ; import { NodeSelection } from "prosemirror-state" ; import { wrappingInputRule , textblockTypeInputRule , InputRule , } from "prosemirror-inputrules" ; import { setBlockType , chainCommands , exitCode , Keymap , } from "prosemirror-commands" ; import * as Uni from "unist" ; import * as Md from "@common/markdown/markdown-ast" ; import { incrHeadingLevelCmd } from "@common/prosemirror/commands/demoteHeadingCmd" ; import { ExtensionNodeAttrs , MdastNodeMap , MdastNodeMapType , NodeExtension , Prose2Mdast_NodeMap , Prose2Mdast_NodeMap_Presets } from "@common/extensions/extension" ; import { makeInlineMathInputRule , makeBlockMathInputRule , REGEX_INLINE_MATH_DOLLARS_ESCAPED , REGEX_BLOCK_MATH_DOLLARS } from "@benrbray/prosemirror-math" ; import YAML from "yaml" ; import { ProseNodeType , ProseSchema } from "@common/types" ; const mac = typeof navigator != "undefined" ? /Mac/ . test ( navigator . platform ) : false ; export class RootExtension extends NodeExtension < Md . Root > { get name ( ) { return "doc" as const ; } createNodeSpec ( ) { return { content : "block+" , attrs : { yamlMeta : { default : { } } } } ; } get mdastNodeType ( ) { return "root" as const ; } ; createMdastMap ( ) { return { mapType : "node_custom" , mapNode : ( _node , children , _ctx , state ) => { let attrs = { yamlMeta : ( state as MdParseState ) . yaml || { } } ; let result = this . nodeType . createAndFill ( attrs , children || undefined ) ; return result ? [ result ] : [ ] ; } } ; } prose2mdast ( ) { return { create : ( node , children ) : [ Md . Root ] => { let rootAttrs = node . attrs as ExtensionNodeAttrs < RootExtension > ; let rootChildren ; if ( Object . keys ( rootAttrs . yamlMeta ) . length > 0 ) { let yamlNode = { type : "yaml" , value : YAML . stringify ( rootAttrs . yamlMeta ) . trim ( ) } ; rootChildren = [ yamlNode , ... children ] ; } else { rootChildren = children ; } let rootNode = { type : this . mdastNodeType , children : rootChildren } ; return [ rootNode as Md . Root ] ; } } ; } } export class ParagraphExtension extends NodeExtension < Md . Paragraph > { get name ( ) { return "paragraph" as const ; } createNodeSpec ( ) { return { content : "inline*" , attrs : { class : { default : undefined } } , group : "block" , parseDOM : [ { tag : "p" } ] , toDOM ( node ) { return [ "p" , { ... ( node . attrs . class && { class : node . attrs . class } ) } , 0 ] ; } } ; } get mdastNodeType ( ) { return "paragraph" as const ; } ; createMdastMap ( ) { return MdastNodeMapType . NODE_DEFAULT ; } prose2mdast ( ) { return Prose2Mdast_NodeMap_Presets . NODE_DEFAULT ; } } export function blockQuoteRule < S extends ProseSchema > ( nodeType ) { return wrappingInputRule ( /^\s*>\s$/ , nodeType ) ; } export class BlockQuoteExtension extends NodeExtension < Md . Blockquote > { get name ( ) { return "blockquote" as const ; } createNodeSpec ( ) { return { content : "block+" , group : "block" , parseDOM : [ { tag : "blockquote" } ] , toDOM ( ) { return [ "blockquote" , 0 ] ; } } ; } createKeymap ( ) { return { "Ctrl->" : setBlockType ( this . nodeType ) } ; } createInputRules ( ) { return [ blockQuoteRule ( this . nodeType ) ] ; } get mdastNodeType ( ) { return "blockquote" as const ; } ; createMdastMap ( ) { return MdastNodeMapType . NODE_DEFAULT ; } prose2mdast ( ) { return Prose2Mdast_NodeMap_Presets . NODE_DEFAULT ; } } export function headingRule < S extends ProseSchema > ( nodeType , maxLevel ) { return textblockTypeInputRule ( new RegExp ( "^(#{1," + maxLevel + "})\\s$" ) , nodeType , match => ( { level : match [ 1 ] . length } ) ) ; } export class HeadingExtension extends NodeExtension < Md . Heading > { get name ( ) { return "heading" as const ; } param _bottomType constructor ( private _bottomType ) { super ( ) ; } createNodeSpec ( ) { return { attrs : { level : { default : 1 } } , content : "(text | image)*" , group : "block" , defining : true , parseDOM : [ { tag : "h1" , attrs : { level : 1 } } , { tag : "h2" , attrs : { level : 2 } } , { tag : "h3" , attrs : { level : 3 } } , { tag : "h4" , attrs : { level : 4 } } , { tag : "h5" , attrs : { level : 5 } } , { tag : "h6" , attrs : { level : 6 } } ] , toDOM ( node ) { return [ "h" + node . attrs . level , 0 ] ; } } ; } createKeymap ( ) { let keymap = { "Tab" : incrHeadingLevelCmd ( + 1 , { requireTextblockStart : false , requireEmptySelection : false } ) , "#" : incrHeadingLevelCmd ( + 1 , { requireTextblockStart : true , requireEmptySelection : true } ) , "Shift-Tab" : incrHeadingLevelCmd ( - 1 , { requireTextblockStart : false , requireEmptySelection : false } , this . _bottomType . nodeType ) , "Backspace" : incrHeadingLevelCmd ( - 1 , { requireTextblockStart : true , requireEmptySelection : true } , this . _bottomType . nodeType ) , } ; for ( let i = 1 ; i <= 6 ; i ++ ) { keymap [ `Shift-Ctrl-${ i }` ] = setBlockType ( this . nodeType , { level : i } ) ; } return keymap ; } createInputRules ( ) { return [ headingRule ( this . nodeType , 6 ) ] ; } get mdastNodeType ( ) { return "heading" as const ; } ; createMdastMap ( ) { return { mapType : "node_custom" , mapNode : ( node , children ) : ProseNode [ ] => { if ( children . length < 1 ) { return [ ] ; } let result = this . nodeType . createAndFill ( { level : node . depth } , children ) ; return result ? [ result ] : [ ] ; } } ; } prose2mdast ( ) { return { create : ( node , children ) : [ Md . Heading ] => { let headingAttrs = node . attrs as ExtensionNodeAttrs < HeadingExtension > ; let headingNode = { type : this . mdastNodeType , children : children , depth : Math . max ( 0 , Math . min ( 6 , headingAttrs . level ) ) as ( 1 | 2 | 3 | 4 | 5 | 6 ) } ; return [ headingNode as Md . Heading ] ; } } ; } } export class HorizontalRuleExtension extends NodeExtension < Md . ThematicBreak > { get name ( ) { return "horizontal_rule" as const ; } createNodeSpec ( ) { return { group : "block" , attrs : { /** Stores the user's original hrule syntax. */ ruleContent : { default : undefined as string | undefined } } , parseDOM : [ { tag : "hr" } ] , toDOM ( ) { return [ "div" , [ "hr" ] ] ; } } ; } createKeymap ( ) { return { "Mod-_" : ( state , dispatch ) => { if ( dispatch ) { dispatch ( state . tr . replaceSelectionWith ( this . nodeType . create ( ) ) . scrollIntoView ( ) ) ; } return true ; } } ; } createInputRules ( ) { return [ ] ; } get mdastNodeType ( ) { return "thematicBreak" as const ; } ; createMdastMap ( ) { return { mapType : "node_custom" , mapNode : ( node , children ) : ProseNode [ ] => { let result = this . nodeType . createAndFill ( { ruleContent : node . ruleContent } ) ; return result ? [ result ] : [ ] ; } } ; } prose2mdast ( ) { return { create : ( node ) : [ Md . ThematicBreak ] => { let ruleAttrs = node . attrs as ExtensionNodeAttrs < HorizontalRuleExtension > ; return [ { type : this . mdastNodeType , ... ( ruleAttrs . ruleContent ? { ruleContent : ruleAttrs . ruleContent } : { } ) , } ] ; } } ; } } export function codeBlockRule < S extends ProseSchema > ( nodeType ) { return textblockTypeInputRule ( /^```$/ , nodeType ) ; } export class CodeBlockExtension extends NodeExtension < Md . Code > { get name ( ) { return "code_block" as const ; } createNodeSpec ( ) { return { content : "text*" , group : "block" , code : true , defining : true , marks : "" , attrs : { params : { default : "" } } , parseDOM : [ { tag : "pre" , preserveWhitespace : ( "full" as "full" ) , getAttrs : ( node ) => ( { params : ( node as HTMLElement ) . getAttribute ( "data-params" ) || "" } ) } ] , toDOM ( node ) { return [ "pre" , { ... ( node . attrs . params && { "data-params" : node . attrs . params } ) } , [ "code" , 0 ] ] ; } } ; } createKeyMap ( ) { return { "Shift-Ctrl-\\" : setBlockType ( this . nodeType ) } ; } createInputRules ( ) { return [ codeBlockRule ( this . nodeType ) ] ; } get mdastNodeType ( ) { return "code" as const ; } ; createMdastMap ( ) { return MdastNodeMapType . NODE_LITERAL ; } prose2mdast ( ) { return Prose2Mdast_NodeMap_Presets . NODE_LIFT_LITERAL ; } } export function orderedListRule < S extends ProseSchema > ( nodeType ) { return wrappingInputRule ( /^(\d+)\.\s$/ , nodeType , match => ( { order : + match [ 1 ] } ) , ( match , node ) => node . childCount + node . attrs . order == + match [ 1 ] ) ; } export class OrderedListExtension extends NodeExtension < Md . List > { get name ( ) { return "ordered_list" as const ; } createNodeSpec ( ) { return { content : "list_item+" , group : "block" , attrs : { order : { default : 1 } , tight : { default : true } } , parseDOM : [ { tag : "ol" , getAttrs ( d ) { let dom = d as HTMLElement ; return { order : + ( ( dom . getAttribute ( "start" ) ) || 1 ) , tight : dom . hasAttribute ( "data-tight" ) } ; } } ] , toDOM ( node ) { return [ "ol" , { ... ( ( node . attrs . order == 1 ) && { start : node . attrs . order } ) , ... ( node . attrs . tight && { "data-tight" : "true" } ) } , 0 ] ; } } ; } createKeymap ( ) { return { "Shift-Ctrl-9" : wrapInList ( this . nodeType ) } ; } createInputRules ( ) { return [ orderedListRule ( this . nodeType ) ] ; } get mdastNodeType ( ) { return "list" as const ; } ; mdastNodeTest ( node ) { return node . ordered === true ; } ; createMdastMap ( ) { return { mapType : "node_custom" , mapNode : ( node , children ) : ProseNode [ ] => { let result = this . nodeType . createAndFill ( { ordered : true , tight : ( node . spread !== true ) } , children || undefined ) ; return result ? [ result ] : [ ] ; } } ; } prose2mdast ( ) { return { create : ( node , children ) : [ Md . List ] => { let listAttrs = node . attrs as ExtensionNodeAttrs < OrderedListExtension > ; let listNode = { type : this . mdastNodeType , children : children , ordered : true , spread : ! listAttrs . tight , start : listAttrs . order , } ; return [ listNode as Md . List ] ; } } ; } } function normalizeBullet ( bullet ) { switch ( bullet ) { case "*" : return null ; case "+" : return "+" ; case "\u2010" : return "\u002D" ; case "\u2011" : return "\u002D" ; case "\u2212" : return "\u002D" ; case "\u002D" : return "\u002D" ; default : return null ; } } export function bulletListRule < S extends ProseSchema > ( nodeType ) { return wrappingInputRule ( /^\s*([-+*])\s$/ , nodeType , ( p ) => { console . log ( p ) ; return ( { bullet : p [ 1 ] } ) ; } , ( p1 , p2 ) => { return p1 [ 1 ] == ( p2 . attrs . bullet || "*" ) ; } ) ; } export class UnorderedListExtension extends NodeExtension < Md . List > { get name ( ) { return "bullet_list" as const ; } createNodeSpec ( ) { return { content : "list_item+" , group : "block" , attrs : { tight : { default : true } , bullet : { default : undefined } } , parseDOM : [ { tag : "ul" , getAttrs : ( dom ) => ( { tight : ( dom as HTMLElement ) . hasAttribute ( "data-tight" ) } ) } ] , toDOM ( node ) { let bullet = normalizeBullet ( node . attrs . bullet || "*" ) ; return [ "ul" , { ... ( node . attrs . tight && { "data-tight" : "true" } ) , ... ( bullet && { "data-bullet" : bullet } ) } , 0 ] ; } } ; } createKeymap ( ) { return { "Shift-Ctrl-8" : wrapInList ( this . nodeType ) } ; } createInputRules ( ) { return [ bulletListRule ( this . nodeType ) ] ; } get mdastNodeType ( ) { return "list" as const ; } ; mdastNodeTest ( node ) { return node . ordered === false ; } ; createMdastMap ( ) { return { mapType : "node_custom" , mapNode : ( node , children ) : ProseNode [ ] => { let result = this . nodeType . createAndFill ( { ordered : false , tight : ( node . spread !== true ) } , children || undefined ) ; return result ? [ result ] : [ ] ; } } ; } prose2mdast ( ) { return { create : ( node , children ) : [ Md . List ] => { let listAttrs = node . attrs as ExtensionNodeAttrs < UnorderedListExtension > ; listAttrs ; let listNode = { type : this . mdastNodeType , children : children , ordered : false , spread : ! listAttrs . tight , } ; return [ listNode as Md . List ] ; } } ; } } export class ListItemExtension extends NodeExtension < Md . ListItem > { get name ( ) { return "list_item" as const ; } createNodeSpec ( ) { return { content : "paragraph block*" , attrs : { class : { default : undefined } , bullet : { default : undefined } } , defining : true , parseDOM : [ { tag : "li" , getAttrs : ( dom ) => ( { bullet : ( dom as HTMLElement ) . dataset . bullet } ) } ] , toDOM ( node ) { let bullet = normalizeBullet ( node . attrs . bullet ) ; return [ "li" , { ... ( node . attrs . class && { class : node . attrs . class } ) , ... ( bullet && { "data-bullet" : bullet } ) } , 0 ] ; } } ; } createKeymap ( ) { return { "Enter" : splitListItem ( this . nodeType ) , "Shift-Tab" : liftListItem ( this . nodeType ) , "Tab" : sinkListItem ( this . nodeType ) } ; } get mdastNodeType ( ) { return "listItem" as const ; } ; createMdastMap ( ) { return { mapType : "node_custom" , mapNode : ( node , children ) : ProseNode [ ] => { let result = this . nodeType . createAndFill ( { ... ( node . marker && { bullet : node . marker } ) } , children || undefined ) ; return result ? [ result ] : [ ] ; } } ; } prose2mdast ( ) { return { create : ( node , children ) : [ Md . ListItem ] => { let itemAttrs = node . attrs as ExtensionNodeAttrs < ListItemExtension > ; let itemNode = { type : this . mdastNodeType , children : children , ... ( itemAttrs ?. bullet ? { marker : itemAttrs . bullet } : { } ) , spread : false , } ; return [ itemNode as Md . ListItem ] ; } } ; } } export class ImageExtension extends NodeExtension < Md . Image > { get name ( ) { return "image" as const ; } createNodeSpec ( ) { return { inline : true , attrs : { src : { } , alt : { default : null } , title : { default : null } } , group : "inline" , draggable : true , parseDOM : [ { tag : "img[src]" , getAttrs ( d ) { let dom = d as HTMLElement ; return { src : dom . getAttribute ( "src" ) , title : dom . getAttribute ( "title" ) , alt : dom . getAttribute ( "alt" ) } ; } } ] , toDOM ( node ) { return [ "img" , node . attrs ] ; } } ; } get mdastNodeType ( ) { return "image" as const ; } ; createMdastMap ( ) { return { mapType : "node_custom" , mapNode : ( node , children ) : ProseNode [ ] => { let result = this . nodeType . createAndFill ( { src : node . url , alt : node . alt , title : node . title } ) ; return result ? [ result ] : [ ] ; } } ; } prose2mdast ( ) { return { create : ( node ) : [ Md . Image ] => { let imageAttrs = node . attrs as { src : string ; alt : string | null ; title : string | null ; } ; return [ { type : this . mdastNodeType , url : imageAttrs . src as string , ... ( imageAttrs . alt ? { alt : imageAttrs . alt } : { } ) , ... ( imageAttrs . title ? { title : imageAttrs . title } : { } ) , } ] ; } } ; } } export class HardBreakExtension extends NodeExtension < Md . Break > { get name ( ) { return "hard_break" as const ; } createNodeSpec ( ) { return { inline : true , group : "inline" , selectable : false , parseDOM : [ { tag : "br" } ] , toDOM ( ) { return [ "br" ] ; } } ; } createKeymap ( ) { let cmd = chainCommands ( exitCode , ( state , dispatch ) => { if ( dispatch ) { dispatch ( state . tr . replaceSelectionWith ( this . nodeType . create ( ) ) . scrollIntoView ( ) ) ; } return true ; } ) ; return { "Mod-Enter" : cmd , "Shift-Enter" : cmd , ... ( mac && { "Ctrl-Enter" : cmd } ) } ; } get mdastNodeType ( ) { return "break" as const ; } ; createMdastMap ( ) { return MdastNodeMapType . NODE_EMPTY ; } prose2mdast ( ) { return Prose2Mdast_NodeMap_Presets . NODE_EMPTY ; } ; } export class InlineMathExtension extends NodeExtension < Md . InlineMath > { get name ( ) { return "math_inline" as const ; } createNodeSpec ( ) { return { group : "inline math" , content : "text*" , inline : true , atom : true , toDOM ( ) { return [ "math-inline" , { class : "math-node" } , 0 ] ; } , parseDOM : [ { tag : "math-inline" } ] } ; } createInputRules ( ) { return [ makeInlineMathInputRule ( REGEX_INLINE_MATH_DOLLARS_ESCAPED , this . nodeType ) ] ; } get mdastNodeType ( ) { return "inlineMath" as const ; } ; createMdastMap ( ) { return MdastNodeMapType . NODE_LITERAL ; } prose2mdast ( ) { return Prose2Mdast_NodeMap_Presets . NODE_LIFT_LITERAL ; } } export class BlockMathExtension extends NodeExtension < Md . BlockMath > { get name ( ) { return "math_display" as const ; } createNodeSpec ( ) { return { group : "block math" , content : "text*" , atom : true , code : true , toDOM ( ) { return [ "math-display" , { class : "math-node" } , 0 ] ; } , parseDOM : [ { tag : "math-display" } ] } ; } createInputRules ( ) { return [ makeBlockMathInputRule ( REGEX_BLOCK_MATH_DOLLARS , this . nodeType ) ] ; } get mdastNodeType ( ) { return "math" as const ; } ; createMdastMap ( ) { return MdastNodeMapType . NODE_LITERAL ; } prose2mdast ( ) { return Prose2Mdast_NodeMap_Presets . NODE_LIFT_LITERAL ; } } export interface ContainerDirective_ProseAttrs { name : string ; attributes : Md . ContainerDirective [ "attributes" ] ; } function makeContainerDirectiveInputRule ( nodeType ) { let pattern = /^\\([a-zA-Z0-9]+) /ig ; const getAttrs = ( matches ) : ContainerDirective_ProseAttrs => { return { name : matches [ 1 ] . trim ( ) || "directive" , attributes : { } } ; } ; const joinPredicate = ( ) => false ; return wrappingInputRule ( pattern , nodeType , getAttrs , joinPredicate ) ; } export class ContainerDirectiveExtension extends NodeExtension < Md . ContainerDirective > { get name ( ) { return "container_directive" as const ; } createNodeSpec ( ) { return { content : "block+" , group : "block" , defining : true , isolating : true , attrs : { name : { } , attributes : { default : { } as Md . ContainerDirective [ "attributes" ] } } , parseDOM : [ { tag : "div.directive" , getAttrs : ( dom ) : ContainerDirective_ProseAttrs => { let elt = dom as HTMLElement ; let name = elt . dataset . name || "directive" ; let attributes = { } ; for ( let attrName of [ "class" , "id" ] ) { let value = elt . getAttribute ( attrName ) ; if ( value === null || value === undefined ) { continue ; } attributes [ attrName ] = value ; } let dataNames = Object . keys ( elt . dataset ) . filter ( name => [ "name" ] . indexOf ( name ) < 0 ) ; for ( let dataName of dataNames ) { let value = elt . dataset [ dataName ] ; if ( value === null || value === undefined ) { continue ; } attributes [ dataName ] = value ; } return { name , attributes } ; } } ] , toDOM ( node ) { let attrs = node . attrs as ContainerDirective_ProseAttrs ; let classNames = attrs . attributes . class ? `directive ${ attrs . attributes . class }` : "directive" ; let dataAttrs = { } ; Object . keys ( attrs . attributes ) . filter ( key => [ "id" , "class" ] . indexOf ( key ) > 0 ) . forEach ( key => { dataAttrs [ `data-${ key }` ] = attrs . attributes [ key ] ; } ) ; return [ "div" , { "data-name" : attrs . name , ... ( attrs . attributes . id && { id : `${ attrs . name }-${ attrs . attributes . id }` } ) , class : classNames } , 0 ] ; } } ; } createInputRules ( ) { return [ makeContainerDirectiveInputRule ( this . nodeType ) ] ; } get mdastNodeType ( ) { return "containerDirective" as const ; } ; createMdastMap ( ) { return { mapType : "node_custom" , mapNode : ( node , children ) : ProseNode [ ] => { let result = this . nodeType . createAndFill ( { name : node . name , attributes : node . attributes } , children || undefined ) ; return result ? [ result ] : [ ] ; } } ; } prose2mdast ( ) { return { create : ( node , children ) : [ Md . ContainerDirective ] => { let attrs = node . attrs as ContainerDirective_ProseAttrs ; let containerNode = { type : this . mdastNodeType , children : children , name : attrs . name , attributes : attrs . attributes } ; return [ containerNode as Md . ContainerDirective ] ; } } ; } } export function inlineInputRule < S extends ProseSchema > ( pattern , nodeType , getAttrs ? ) { return new InputRule ( pattern , ( state , match , start , end ) => { let $start = state . doc . resolve ( start ) ; let index = $start . index ( ) ; let $end = state . doc . resolve ( end ) ; let attrs = getAttrs instanceof Function ? getAttrs ( match ) : getAttrs ; if ( ! $start . parent . canReplaceWith ( index , $end . index ( ) , nodeType ) ) { return null ; } return state . tr . replaceRangeWith ( start , end , nodeType . create ( attrs , nodeType . schema . text ( match [ 1 ] ) ) ) ; } ) ; } import { MdParseState , AnyChildren } from "@common/markdown/mdast2prose" ; export function citationRule < S extends ProseSchema > ( nodeType ) { return inlineInputRule ( /@\[([^\s](?:[^\]]*[^\s])?)\](.)$/ , nodeType ) ; } export class CitationExtension extends NodeExtension < Md . Cite > { get name ( ) { return "citation" as const ; } createNodeSpec ( ) { return { content : "text*" , group : "inline" , inline : true , atom : true , attrs : { /** [@citation] if true, otherwise @[citation] */ pandocSyntax : { default : false } } , parseDOM : [ { tag : "span.citation" , getAttrs ( node ) { return { pandocSyntax : ( node as Element ) . classList . contains ( "citation-pandoc" ) } ; } } ] , toDOM ( node ) { let pandocSytnax = ( node . attrs . pandocSyntax === true ) ; let attrs = { class : pandocSytnax ? "citation citation-pandoc" : "citation citation-alt" } ; return [ "span" , attrs , 0 ] ; } } ; } createKeymap ( ) { return { } ; } createInputRules ( ) { return [ citationRule ( this . nodeType ) ] ; } get mdastNodeType ( ) { return "cite" as const ; } ; createMdastMap ( ) { return { mapType : "node_custom" , mapNode : ( node , _ ) : ProseNode [ ] => { let text = node . value ; let attrs = { pandocSyntax : false } ; let start = 0 ; let end = text . length ; if ( text [ 0 ] == "@" && text [ 1 ] == "[" ) { start = 2 ; attrs . pandocSyntax = false ; } else if ( text [ 0 ] == "[" ) { start = 1 ; attrs . pandocSyntax = true ; } else { console . error ( `invalid citation syntax: ${ text }` ) ; } if ( text [ end - 1 ] == "]" ) { end -- ; } let textNode = this . store . schema . text ( text . slice ( start , end ) ) ; let result = this . nodeType . createAndFill ( attrs , [ textNode ] ) ; return result ? [ result ] : [ ] ; } } ; } prose2mdast ( ) { return { create : ( node ) : [ Md . Cite ] => { let citeAttrs = node . attrs as ExtensionNodeAttrs < CitationExtension > ; let citeSyntax ; if ( citeAttrs . pandocSyntax ) { citeSyntax = `[${ node . textContent }]` ; } else { citeSyntax = `@[${ node . textContent }]` ; } return [ { type : "cite" , value : citeSyntax , altSyntax : ( citeAttrs . pandocSyntax !== true ) , data : { citeItems : [ ] } } ] ; } } ; } }