import { FormatConverter } from './format' ; import { AnkiConnectNote , AnkiConnectNoteAndID } from './interfaces/note-interface' ; import { FIELDS_DICT , FROZEN_FIELDS_DICT } from './interfaces/field-interface' ; import { FileData } from './interfaces/settings-interface' ; const TAG_PREFIX = "Tags: " ; export const TAG_SEP = " " ; export const ID_REGEXP_STR = String . raw `\n?(?:<!--)?(?:ID: (\d+).*)` ; export const TAG_REGEXP_STR = String . raw `(Tags: .*)` ; const OBS_TAG_REGEXP = /#(\w+)/g ; const ANKI_CLOZE_REGEXP = /{{c\d+::[\s\S]+?}}/ ; export const CLOZE_ERROR = 42 ; export const NOTE_TYPE_ERROR = 69 ; function has_clozes ( text ) { return ANKI_CLOZE_REGEXP . test ( text ) ; } function note_has_clozes ( note ) { for ( let i in note . fields ) { if ( has_clozes ( note . fields [ i ] ) ) { return true ; } } return false ; } abstract class AbstractNote { text : string ; split_text : string [ ] ; current_field_num : number ; delete : boolean ; identifier : number | null ; tags : string [ ] ; note_type : string ; field_names : string [ ] ; current_field : string ; ID_REGEXP : RegExp = /(?:<!--)?ID: (\d+)/ ; formatter : FormatConverter ; curly_cloze : boolean ; highlights_to_cloze : boolean ; no_note_type : boolean ; constructor ( note_text , fields_dict , curly_cloze , highlights_to_cloze , formatter ) { this . text = note_text . trim ( ) ; this . current_field_num = 0 ; this . delete = false ; this . no_note_type = false ; this . split_text = this . getSplitText ( ) ; this . identifier = this . getIdentifier ( ) ; this . tags = this . getTags ( ) ; this . note_type = this . getNoteType ( ) ; if ( ! ( fields_dict . hasOwnProperty ( this . note_type ) ) ) { this . no_note_type = true ; return ; } this . field_names = fields_dict [ this . note_type ] ; this . current_field = this . field_names [ 0 ] ; this . formatter = formatter ; this . curly_cloze = curly_cloze ; this . highlights_to_cloze = highlights_to_cloze ; } abstract getSplitText ( ) ; abstract getIdentifier ( ) ; abstract getTags ( ) ; abstract getNoteType ( ) ; abstract getFields ( ) ; parse ( deck , url , frozen_fields_dict , data , context ) { let template = JSON . parse ( JSON . stringify ( data . template ) ) ; template [ "modelName" ] = this . note_type ; if ( this . no_note_type ) { return { note : template , identifier : NOTE_TYPE_ERROR } ; } template [ "fields" ] = this . getFields ( ) ; const file_link_fields = data . file_link_fields ; if ( url ) { this . formatter . format_note_with_url ( template , url , file_link_fields [ this . note_type ] ) ; } if ( Object . keys ( frozen_fields_dict ) . length ) { this . formatter . format_note_with_frozen_fields ( template , frozen_fields_dict ) ; } if ( context ) { const context_field = data . context_fields [ this . note_type ] ; template [ "fields" ] [ context_field ] += context ; } if ( data . add_obs_tags ) { for ( let key in template [ "fields" ] ) { for ( let match of template [ "fields" ] [ key ] . matchAll ( OBS_TAG_REGEXP ) ) { this . tags . push ( match [ 1 ] ) ; } template [ "fields" ] [ key ] = template [ "fields" ] [ key ] . replace ( OBS_TAG_REGEXP , "" ) ; } } template [ "tags" ] . push ( ... this . tags ) ; template [ "deckName" ] = deck ; return { note : template , identifier : this . identifier } ; } } export class Note extends AbstractNote { getSplitText ( ) { return this . text . split ( "\n" ) ; } getIdentifier ( ) { if ( this . ID_REGEXP . test ( this . split_text [ this . split_text . length - 1 ] ) ) { return parseInt ( this . ID_REGEXP . exec ( this . split_text . pop ( ) ) [ 1 ] ) ; } else { return null ; } } getTags ( ) { if ( this . split_text [ this . split_text . length - 1 ] . startsWith ( TAG_PREFIX ) ) { return this . split_text . pop ( ) . slice ( TAG_PREFIX . length ) . split ( TAG_SEP ) ; } else { return [ ] ; } } getNoteType ( ) { return this . split_text [ 0 ] ; } fieldFromLine ( line ) { for ( let field of this . field_names ) { if ( line . startsWith ( field + ":" ) ) { return [ line . slice ( ( field + ":" ) . length ) , field ] ; } } return [ line , this . current_field ] ; } getFields ( ) { let fields = { } ; for ( let field of this . field_names ) { fields [ field ] = "" ; } for ( let line of this . split_text . slice ( 1 ) ) { [ line , this . current_field ] = this . fieldFromLine ( line ) ; fields [ this . current_field ] += line + "\n" ; } for ( let key in fields ) { fields [ key ] = this . formatter . format ( fields [ key ] . trim ( ) , this . note_type . includes ( "Cloze" ) && this . curly_cloze , this . highlights_to_cloze ) . trim ( ) ; } return fields ; } } export class InlineNote extends AbstractNote { static TAG_REGEXP : RegExp = /Tags: (.*)/ ; static ID_REGEXP : RegExp = /(?:<!--)?ID: (\d+)/ ; static TYPE_REGEXP : RegExp = /\[(.*?)\]/ ; getSplitText ( ) { return this . text . split ( " " ) ; } getIdentifier ( ) { const result = this . text . match ( InlineNote . ID_REGEXP ) ; if ( result ) { this . text = this . text . slice ( 0 , result . index ) . trim ( ) ; return parseInt ( result [ 1 ] ) ; } else { return null ; } } getTags ( ) { const result = this . text . match ( InlineNote . TAG_REGEXP ) ; if ( result ) { this . text = this . text . slice ( 0 , result . index ) . trim ( ) ; return result [ 1 ] . split ( TAG_SEP ) ; } else { return [ ] ; } } getNoteType ( ) { const result = this . text . match ( InlineNote . TYPE_REGEXP ) ; this . text = this . text . slice ( result . index + result [ 0 ] . length ) ; return result [ 1 ] ; } getFields ( ) { let fields = { } ; for ( let field of this . field_names ) { fields [ field ] = "" ; } for ( let word of this . text . split ( " " ) ) { for ( let field of this . field_names ) { if ( word === field + ":" ) { this . current_field = field ; word = "" ; } } fields [ this . current_field ] += word + " " ; } for ( let key in fields ) { fields [ key ] = this . formatter . format ( fields [ key ] . trim ( ) , this . note_type . includes ( "Cloze" ) && this . curly_cloze , this . highlights_to_cloze ) . trim ( ) ; } return fields ; } } export class RegexNote { match : RegExpMatchArray ; note_type : string ; groups : Array < string > ; identifier : number | null ; tags : string [ ] ; field_names : string [ ] ; curly_cloze : boolean ; highlights_to_cloze : boolean ; formatter : FormatConverter ; constructor ( match , note_type , fields_dict , tags , id , curly_cloze , highlights_to_cloze , formatter ) { this . match = match ; this . note_type = note_type ; this . identifier = id ? parseInt ( this . match . pop ( ) ) : null ; this . tags = tags ? this . match . pop ( ) . slice ( TAG_PREFIX . length ) . split ( TAG_SEP ) : [ ] ; this . field_names = fields_dict [ note_type ] ; this . curly_cloze = curly_cloze ; this . formatter = formatter ; this . highlights_to_cloze = highlights_to_cloze ; } getFields ( ) { let fields = { } ; for ( let field of this . field_names ) { fields [ field ] = "" ; } for ( let index in this . match . slice ( 1 ) ) { fields [ this . field_names [ index ] ] = this . match . slice ( 1 ) [ index ] ? this . match . slice ( 1 ) [ index ] : "" ; } for ( let key in fields ) { fields [ key ] = this . formatter . format ( fields [ key ] . trim ( ) , this . note_type . includes ( "Cloze" ) && this . curly_cloze , this . highlights_to_cloze ) . trim ( ) ; } return fields ; } parse ( deck , url = "" , frozen_fields_dict , data , context ) { let template = JSON . parse ( JSON . stringify ( data . template ) ) ; template [ "modelName" ] = this . note_type ; template [ "fields" ] = this . getFields ( ) ; const file_link_fields = data . file_link_fields ; if ( url ) { this . formatter . format_note_with_url ( template , url , file_link_fields [ this . note_type ] ) ; } if ( Object . keys ( frozen_fields_dict ) . length ) { this . formatter . format_note_with_frozen_fields ( template , frozen_fields_dict ) ; } if ( context ) { const context_field = data . context_fields [ this . note_type ] ; template [ "fields" ] [ context_field ] += context ; } if ( this . note_type . includes ( "Cloze" ) && ! ( note_has_clozes ( template ) ) ) { this . identifier = CLOZE_ERROR ; } template [ "tags" ] . push ( ... this . tags ) ; template [ "deckName" ] = deck ; return { note : template , identifier : this . identifier } ; } }