import { ApplySchemaAttributes , assertGet , command , CommandFunction , ExtensionPriority , ExtensionTag , findParentNodeOfType , getMatchString , isElementDomNode , KeyBindings , NodeExtension , NodeExtensionSpec , NodeSpecOverride , NodeViewMethod , ProsemirrorAttributes , ProsemirrorNode , } from '@remirror/core' ; import { InputRule , wrappingInputRule } from '@remirror/pm/inputrules' ; import { ResolvedPos } from '@remirror/pm/model' ; import { ExtensionListTheme } from '@remirror/theme' ; import { splitListItem , wrapSelectedItems } from './list-commands' ; import { createCustomMarkListItemNodeView } from './list-item-node-view' ; import { ListItemSharedExtension } from './list-item-shared-extension' ; /**
 * Creates the node for a task list item.
 */ export class TaskListItemExtension extends NodeExtension { get name ( ) { return 'taskListItem' as const ; } createTags ( ) { return [ ExtensionTag . ListItemNode ] ; } createNodeSpec ( extra , override ) { return { content : 'paragraph block*' , defining : true , draggable : false , ... override , attrs : { ... extra . defaults ( ) , checked : { default : false } , } , parseDOM : [ { tag : 'li[data-task-list-item]' , getAttrs : ( node ) => { let checked = false ; if ( isElementDomNode ( node ) && node . getAttribute ( 'data-checked' ) !== null ) { checked = true ; } return { checked , ... extra . parse ( node ) , } ; } , priority : ExtensionPriority . Medium , } , ... ( override . parseDOM ?? [ ] ) , ] , toDOM : ( node ) => { return [ 'li' , { ... extra . dom ( node ) , 'data-task-list-item' : '' , 'data-checked' : node . attrs . checked ? '' : undefined , } , 0 , ] ; } , } ; } createNodeViews ( ) { return ( node , view , getPos ) => { const mark = document . createElement ( 'input' ) ; mark . type = 'checkbox' ; mark . classList . add ( ExtensionListTheme . LIST_ITEM_CHECKBOX ) ; mark . contentEditable = 'false' ; mark . addEventListener ( 'click' , ( ) => { const pos = ( getPos as ( ) => number ) ( ) ; const $pos = view . state . doc . resolve ( pos + 1 ) ; this . store . commands . toggleCheckboxChecked ( { $pos } ) ; return true ; } ) ; return createCustomMarkListItemNodeView ( { node , mark , updateDOM : updateNodeViewDOM , updateMark : updateNodeViewMark , } ) ; } ; } createKeymap ( ) { return { Enter : splitListItem ( this . type ) , } ; } createExtensions ( ) { return [ new ListItemSharedExtension ( ) ] ; } param checked param selection @ command ( ) toggleCheckboxChecked ( props ? ) { let checked ; let $pos ; if ( typeof props === 'boolean' ) { checked = props ; } else if ( props ) { checked = props . checked ; $pos = props . $pos ; } return ( { tr , dispatch } ) => { const found = findParentNodeOfType ( { selection : $pos ?? tr . selection . $from , types : this . type , } ) ; if ( ! found ) { return false ; } const { node , pos } = found ; const attrs = { ... node . attrs , checked : checked ?? ! node . attrs . checked } ; dispatch ?. ( tr . setNodeMarkup ( pos , undefined , attrs ) ) ; return true ; } ; } createInputRules ( ) { const regexp = /^\s*(\[( ?|x|X)]\s)$/ ; return [ wrappingInputRule ( regexp , this . type , ( match ) => { return { checked : [ 'x' , 'X' ] . includes ( getMatchString ( match , 2 ) ) } ; } ) , new InputRule ( regexp , ( state , match , start , end ) => { const tr = state . tr ; tr . deleteRange ( start , end ) ; const canUpdate = wrapSelectedItems ( { listType : assertGet ( this . store . schema . nodes , 'taskList' ) , itemType : this . type , tr , } ) ; if ( ! canUpdate ) { return null ; } const checked = [ 'x' , 'X' ] . includes ( getMatchString ( match , 2 ) ) ; if ( checked ) { const found = findParentNodeOfType ( { selection : tr . selection , types : this . type } ) ; if ( found ) { tr . setNodeMarkup ( found . pos , undefined , { checked } ) ; } } return tr ; } ) , ] ; } } function updateNodeViewDOM ( node , dom ) { node . attrs . checked ? dom . setAttribute ( 'data-checked' , '' ) : dom . removeAttribute ( 'data-checked' ) ; dom . setAttribute ( 'data-task-list-item' , '' ) ; } function updateNodeViewMark ( node , mark ) { ( mark as HTMLInputElement ) . checked = ! ! node . attrs . checked ; } export type TaskListItemAttributes = ProsemirrorAttributes < { default checked : boolean ; } > ; declare global { namespace Remirror { interface AllExtensions { taskListItem : TaskListItemExtension ; } } }