import { animate , state , style , transition , trigger } from '@angular/animations' ; import { Component , HostListener , NgZone , OnDestroy , OnInit , ViewEncapsulation } from '@angular/core' ; import { MatDialog , MatDialogRef } from '@angular/material/dialog' ; import { ActivatedRoute } from '@angular/router' ; import { BrowserWindow , remote , SaveDialogOptions , SaveDialogReturnValue } from 'electron' ; import * as electronLocalshortcut from 'electron-localshortcut' ; import * as fs from 'fs-extra' ; import * as path from 'path' ; import * as Quill from 'quill' ; import BlotFormatter from 'quill-blot-formatter' ; import { Subject } from 'rxjs' ; import { debounceTime } from 'rxjs/internal/operators' ; import { ClipboardManager } from '../../core/clipboard-manager' ; import { Constants } from '../../core/constants' ; import { Operation } from '../../core/enums' ; import { Logger } from '../../core/logger' ; import { NoteExport } from '../../core/note-export' ; import { ProductInformation } from '../../core/product-information' ; import { Settings } from '../../core/settings' ; import { TasksCount } from '../../core/tasks-count' ; import { Utils } from '../../core/utils' ; import { WorkerManager } from '../../core/worker-manager' ; import { AppearanceService } from '../../services/appearance/appearance.service' ; import { NoteDetailsResult } from '../../services/results/note-details-result' ; import { NoteOperationResult } from '../../services/results/note-operation-result' ; import { SnackBarService } from '../../services/snack-bar/snack-bar.service' ; import { TranslatorService } from '../../services/translator/translator.service' ; import { ConfirmationDialogComponent } from '../dialogs/confirmation-dialog/confirmation-dialog.component' ; import { ErrorDialogComponent } from '../dialogs/error-dialog/error-dialog.component' ; import { CustomImageSpec } from './custom-image-spec' ; @ Component ( { selector : 'app-note' , templateUrl : './note.component.html' , styleUrls : [ './note.component.scss' ] , encapsulation : ViewEncapsulation . None , animations : [ trigger ( 'actionIconRotation' , [ state ( 'default' , style ( { transform : 'rotate(0)' } ) ) , state ( 'rotated' , style ( { transform : 'rotate(90deg)' } ) ) , transition ( 'rotated => default' , animate ( '250ms ease-out' ) ) , transition ( 'default => rotated' , animate ( '250ms ease-in' ) ) , ] ) , ] , } ) export class NoteComponent implements OnInit , OnDestroy { private saveTimeoutMilliseconds : number = 5000 ; private windowCloseTimeoutMilliseconds : number = 500 ; private quill : Quill ; private globalEmitter : any = remote . getGlobal ( 'globalEmitter' ) ; private isTitleDirty : boolean = false ; private isTextDirty : boolean = false ; private noteMarkChangedListener : any = this . noteMarkChangedHandler . bind ( this ) ; private focusNoteListener : any = this . focusNoteHandler . bind ( this ) ; private closeNoteListener : any = this . closeNoteHandler . bind ( this ) ; private languageChangedListener : any = this . languageChangedHandler . bind ( this ) ; private fontSizeChangedListener : any = this . fontSizeChangedHandler . bind ( this ) ; private contextMenu : any ; private cutContextMenuItem : any ; private copyContextMenuItem : any ; private pasteContextMenuItem : any ; private deleteContextMenuItem : any ; constructor ( private activatedRoute , private zone , private dialog , private logger , private snackBar , private translator , private settings , public appearance , private clipboard , private worker ) { } public noteId : string ; public initialNoteTitle : string ; public noteTitle : string ; public isMarked : boolean ; public noteTitleChanged : Subject < string > = new Subject < string > ( ) ; public noteTextChanged : Subject < string > = new Subject < string > ( ) ; public saveChangesAndCloseNoteWindow : Subject < string > = new Subject < string > ( ) ; public canPerformActions : boolean = false ; public isBusy : boolean = false ; public actionIconRotation : string = 'default' ; public canSearch : boolean = false ; public ngOnDestroy ( ) { } public async ngOnInit ( ) { this . setEditorFontSize ( ) ; this . addContextMenuAsync ( ) ; const notePlaceHolder = await this . translator . getAsync ( 'Notes.NotePlaceholder' ) ; const icons = Quill . import ( 'ui/icons' ) ; icons [ 'undo' ] = `<svg class="custom-undo" viewbox="0 0 18 18">
    <polygon class="ql-fill ql-stroke" points="6 10 4 12 2 10 6 10"></polygon>
    <path class="ql-stroke" d="M8.09,13.91A4.6,4.6,0,0,0,9,14,5,5,0,1,0,4,9"></path>
  </svg>` ; icons [ 'redo' ] = `<svg class="custom-redo" viewbox="0 0 18 18">
    <polygon class="ql-fill ql-stroke" points="12 10 14 12 16 10 12 10"></polygon>
    <path class="ql-stroke" d="M9.91,13.91A4.6,4.6,0,0,1,9,14a5,5,0,1,1,5-5"></path>
  </svg>` ; Quill . register ( 'modules/blotFormatter' , BlotFormatter ) ; this . quill = new Quill ( '#editor' , { modules : { toolbar : { container : [ [ 'bold' , 'italic' , 'underline' , 'strike' ] , [ 'undo' , 'redo' ] , [ { background : [ ] } , { header : 1 } , { header : 2 } , { list : 'ordered' } , { list : 'bullet' } , { list : 'check' } , 'link' , 'blockquote' , 'code-block' , 'image' , 'clean' , ] , ] , handlers : { undo : this . performUndo , redo : this . performRedo , } , } , blotFormatter : { specs : [ CustomImageSpec ] , } , } , placeholder : notePlaceHolder , theme : 'snow' , } ) ; await this . setToolbarTooltipsAsync ( ) ; this . quill . on ( 'text-change' , ( ) => { this . isTextDirty = true ; this . clearSearch ( ) ; this . noteTextChanged . next ( '' ) ; } ) ; this . quill . clipboard . addMatcher ( Node . ELEMENT_NODE , ( node , delta ) => { const plaintext = node . innerText ; const Delta = Quill . import ( 'delta' ) ; return new Delta ( ) . insert ( plaintext ) ; } ) ; this . quill . keyboard . addBinding ( { key : 'Y' , ctrlKey : true , handler : ( ) => { this . performRedo ( ) ; } , } ) ; this . activatedRoute . queryParams . subscribe ( async ( params ) => { this . noteId = params [ 'id' ] ; this . addListeners ( ) ; await this . getNoteDetailsAsync ( ) ; this . applySearch ( ) ; } ) ; this . noteTitleChanged . pipe ( debounceTime ( this . saveTimeoutMilliseconds ) ) . subscribe ( ( finalNoteTitle ) => { this . globalEmitter . emit ( Constants . setNoteTitleEvent , this . noteId , this . initialNoteTitle , finalNoteTitle , this . setNoteTitleCallbackAsync . bind ( this ) ) ; } ) ; this . noteTextChanged . pipe ( debounceTime ( this . saveTimeoutMilliseconds ) ) . subscribe ( async ( _ ) => { this . globalEmitter . emit ( Constants . setNoteTextEvent , this . noteId , this . quill . getText ( ) , this . getTasksCount ( ) , this . setNoteTextCallbackAsync . bind ( this ) ) ; } ) ; this . saveChangesAndCloseNoteWindow . pipe ( debounceTime ( this . windowCloseTimeoutMilliseconds ) ) . subscribe ( ( _ ) => { this . saveAndClose ( ) ; } ) ; document . onpaste = ( e ) => { if ( this . clipboard . containsImage ( ) ) { e . preventDefault ( ) ; this . pasteImageFromClipboard ( ) ; } } ; const window = remote . getCurrentWindow ( ) ; electronLocalshortcut . register ( window , 'ESC' , ( ) => { if ( this . settings . closeNotesWithEscape ) { window . close ( ) ; } } ) ; } private performUndo ( ) { if ( this . quill != undefined && this . quill . history != undefined ) { try { this . quill . history . undo ( ) ; } catch ( error ) { this . logger . error ( `Could not perform undo. Cause: ${ error }` , 'NoteComponent' , 'performUndo' ) ; } } } private performRedo ( ) { if ( this . quill != undefined && this . quill . history != undefined ) { try { this . quill . history . redo ( ) ; } catch ( error ) { this . logger . error ( `Could not perform redo. Cause: ${ error }` , 'NoteComponent' , 'performRedo' ) ; } } } private async setToolbarTooltipsAsync ( ) { const toolbarElement = document . querySelector ( '.ql-toolbar' ) ; toolbarElement . querySelector ( 'span.ql-background' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Highlight' ) ) ; toolbarElement . querySelector ( 'button.ql-undo' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Undo' ) ) ; toolbarElement . querySelector ( 'button.ql-redo' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Redo' ) ) ; toolbarElement . querySelector ( 'button.ql-bold' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Bold' ) ) ; toolbarElement . querySelector ( 'button.ql-italic' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Italic' ) ) ; toolbarElement . querySelector ( 'button.ql-underline' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Underline' ) ) ; toolbarElement . querySelector ( 'button.ql-strike' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Strikethrough' ) ) ; toolbarElement . querySelector ( '[class="ql-header"][value="1"]' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Heading1' ) ) ; toolbarElement . querySelector ( '[class="ql-header"][value="2"]' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Heading2' ) ) ; toolbarElement . querySelector ( '[class="ql-list"][value="ordered"]' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.NumberedList' ) ) ; toolbarElement . querySelector ( '[class="ql-list"][value="bullet"]' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.BulletedList' ) ) ; toolbarElement . querySelector ( '[class="ql-list"][value="check"]' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.TaskList' ) ) ; toolbarElement . querySelector ( 'button.ql-link' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Link' ) ) ; toolbarElement . querySelector ( 'button.ql-blockquote' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Quote' ) ) ; toolbarElement . querySelector ( 'button.ql-code-block' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Code' ) ) ; toolbarElement . querySelector ( 'button.ql-image' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.Image' ) ) ; toolbarElement . querySelector ( 'button.ql-clean' ) . setAttribute ( 'title' , await this . translator . getAsync ( 'Tooltips.ClearFormatting' ) ) ; } public onNotetitleChange ( newNoteTitle ) { this . isTitleDirty = true ; this . clearSearch ( ) ; this . noteTitleChanged . next ( newNoteTitle ) ; } @ HostListener ( 'window:beforeunload' , [ '$event' ] ) public beforeunloadHandler ( event ) { this . logger . info ( `Detected closing of note with id=${ this . noteId }` , 'NoteComponent' , 'beforeunloadHandler' ) ; if ( this . isTitleDirty || this . isTextDirty ) { this . isTitleDirty = false ; this . isTextDirty = false ; this . logger . info ( `Note with id=${ this . noteId } is dirty. Preventing close to save changes first.` , 'NoteComponent' , 'beforeunloadHandler' ) ; event . preventDefault ( ) ; event . returnValue = '' ; this . saveChangesAndCloseNoteWindow . next ( '' ) ; } else { this . logger . info ( `Note with id=${ this . noteId } is clean. Closing directly.` , 'NoteComponent' , 'beforeunloadHandler' ) ; this . cleanup ( ) ; } } public onTitleKeydown ( event ) { if ( event . key === 'Enter' || event . key === 'Tab' ) { event . preventDefault ( ) ; this . quill . setSelection ( 0 , 0 ) ; } } public toggleNoteMark ( ) { this . hideActionButtonsDelayedAsync ( ) ; this . globalEmitter . emit ( Constants . setNoteMarkEvent , this . noteId , ! this . isMarked ) ; } public async exportNoteToPdfAsync ( ) { this . hideActionButtons ( ) ; const options = { defaultPath : Utils . getPdfExportPath ( remote . app . getPath ( 'documents' ) , this . noteTitle ) } ; const saveDialogReturnValue = await remote . dialog . showSaveDialog ( null , options ) ; if ( saveDialogReturnValue . filePath != undefined ) { this . worker . exportToPdf ( saveDialogReturnValue . filePath , this . noteTitle , this . quill . root . innerHTML ) ; } } public printNote ( ) { this . hideActionButtons ( ) ; this . worker . print ( this . noteTitle , this . quill . root . innerHTML ) ; } public async deleteNoteAsync ( ) { this . hideActionButtons ( ) ; const title = await this . translator . getAsync ( 'DialogTitles.ConfirmDeleteNote' ) ; const text = await this . translator . getAsync ( 'DialogTexts.ConfirmDeleteNote' , { noteTitle : this . noteTitle } ) ; const dialogRef = this . dialog . open ( ConfirmationDialogComponent , { width : '450px' , data : { dialogTitle : title , dialogText : text } , } ) ; dialogRef . afterClosed ( ) . subscribe ( async ( result ) => { if ( result ) { this . globalEmitter . emit ( Constants . deleteNoteEvent , this . noteId ) ; const window = remote . getCurrentWindow ( ) ; window . close ( ) ; } } ) ; } public onFixedContentClick ( ) { this . hideActionButtons ( ) ; } public toggleShowActions ( ) { this . canPerformActions = ! this . canPerformActions ; this . rotateActionsButton ( ) ; } public rotateActionsButton ( ) { this . actionIconRotation = this . canPerformActions ? 'rotated' : 'default' ; } public async exportNoteAsync ( ) { this . hideActionButtons ( ) ; this . isBusy = true ; const options = { defaultPath : Utils . getNoteExportPath ( remote . app . getPath ( 'documents' ) , this . noteTitle ) } ; const saveDialogReturnValue = await remote . dialog . showSaveDialog ( null , options ) ; const noteExport = new NoteExport ( this . noteTitle , this . quill . getText ( ) , JSON . stringify ( this . quill . getContents ( ) ) ) ; try { if ( saveDialogReturnValue . filePath != undefined ) { await fs . writeFile ( saveDialogReturnValue . filePath , JSON . stringify ( noteExport ) ) ; this . snackBar . noteExportedAsync ( this . noteTitle ) ; } this . isBusy = false ; } catch ( error ) { this . isBusy = false ; this . logger . error ( `An error occurred while exporting the note with title '${ this . noteTitle }'. Cause: ${ error }` , 'NoteComponent' , 'exportNoteAsync' ) ; const errorText = await this . translator . getAsync ( 'ErrorTexts.ExportNoteError' , { noteTitle : this . noteTitle } ) ; this . dialog . open ( ErrorDialogComponent , { width : '450px' , data : { errorText : errorText } , } ) ; } } private async addContextMenuAsync ( ) { this . contextMenu = new remote . Menu ( ) ; this . cutContextMenuItem = new remote . MenuItem ( { label : await this . translator . getAsync ( 'ContextMenu.Cut' ) , click : ( ) => { this . performCut ( ) ; } , } ) ; this . copyContextMenuItem = new remote . MenuItem ( { label : await this . translator . getAsync ( 'ContextMenu.Copy' ) , click : ( ) => { this . performCopy ( ) ; } , } ) ; this . pasteContextMenuItem = new remote . MenuItem ( { label : await this . translator . getAsync ( 'ContextMenu.Paste' ) , click : ( ) => { this . performPaste ( ) ; } , } ) ; this . deleteContextMenuItem = new remote . MenuItem ( { label : await this . translator . getAsync ( 'ContextMenu.Delete' ) , click : ( ) => { this . performDelete ( ) ; } , } ) ; this . contextMenu . append ( this . cutContextMenuItem ) ; this . contextMenu . append ( this . copyContextMenuItem ) ; this . contextMenu . append ( this . pasteContextMenuItem ) ; this . contextMenu . append ( this . deleteContextMenuItem ) ; const editor = document . getElementById ( 'editor' ) ; editor . removeEventListener ( 'contextmenu' , this . contextMenuListener . bind ( this ) ) ; editor . addEventListener ( 'contextmenu' , this . contextMenuListener . bind ( this ) , false ) ; } private contextMenuListener ( e ) { e . preventDefault ( ) ; this . updateContextMenuItemsEnabledState ( ) ; this . contextMenu . popup ( { window : remote . getCurrentWindow ( ) } ) ; } private hasSelectedRange ( ) { const range = this . quill . getSelection ( ) ; if ( range && range . length > 0 ) { return true ; } return false ; } private updateContextMenuItemsEnabledState ( ) { const hasSelectedText = this . hasSelectedRange ( ) ; this . cutContextMenuItem . enabled = hasSelectedText ; this . copyContextMenuItem . enabled = hasSelectedText ; this . deleteContextMenuItem . enabled = hasSelectedText ; this . pasteContextMenuItem . enabled = this . clipboard . containsText ( ) || this . clipboard . containsImage ( ) ; } private performCut ( ) { const range = this . quill . getSelection ( ) ; if ( ! range || range . length === 0 ) { return ; } const text = this . quill . getText ( range . index , range . length ) ; this . clipboard . writeText ( text ) ; this . quill . deleteText ( range . index , range . length ) ; } private performCopy ( ) { const range = this . quill . getSelection ( ) ; if ( ! range || range . length === 0 ) { return ; } const text = this . quill . getText ( range . index , range . length ) ; this . clipboard . writeText ( text ) ; } private pasteImageFromClipboard ( ) { try { this . insertImage ( this . clipboard . readImage ( ) ) ; } catch ( error ) { this . logger . error ( 'Could not paste as image' , 'NoteComponent' , 'performPaste' ) ; } } private pastTextFromClipboard ( ) { const range = this . quill . getSelection ( ) ; if ( ! range ) { return ; } const clipboardText = this . clipboard . readText ( ) ; if ( clipboardText ) { this . quill . insertText ( range . index , clipboardText ) ; } } private performPaste ( ) { if ( this . clipboard . containsImage ( ) ) { this . pasteImageFromClipboard ( ) ; } else { this . pastTextFromClipboard ( ) ; } } private performDelete ( ) { const range = this . quill . getSelection ( ) ; if ( ! range || range . length === 0 ) { return ; } this . quill . deleteText ( range . index , range . length ) ; } private removeListeners ( ) { this . globalEmitter . removeListener ( Constants . noteMarkChangedEvent , this . noteMarkChangedListener ) ; this . globalEmitter . removeListener ( Constants . focusNoteEvent , this . focusNoteListener ) ; this . globalEmitter . removeListener ( Constants . closeNoteEvent , this . closeNoteListener ) ; this . globalEmitter . removeListener ( Constants . languageChangedEvent , this . languageChangedListener ) ; this . globalEmitter . removeListener ( Constants . noteFontSizeChangedEvent , this . fontSizeChangedListener ) ; } private addListeners ( ) { this . globalEmitter . on ( Constants . noteMarkChangedEvent , this . noteMarkChangedListener ) ; this . globalEmitter . on ( Constants . focusNoteEvent , this . focusNoteListener ) ; this . globalEmitter . on ( Constants . closeNoteEvent , this . closeNoteListener ) ; this . globalEmitter . on ( Constants . languageChangedEvent , this . languageChangedListener ) ; this . globalEmitter . on ( Constants . noteFontSizeChangedEvent , this . fontSizeChangedListener ) ; } private cleanup ( ) { this . globalEmitter . emit ( Constants . setNoteOpenEvent , this . noteId , false ) ; this . removeListeners ( ) ; } private insertImage ( file ) { const reader = new FileReader ( ) ; reader . onload = ( e ) => { const img = document . createElement ( 'img' ) ; img . src = e . target . result ; const range = window . getSelection ( ) . getRangeAt ( 0 ) ; range . deleteContents ( ) ; range . insertNode ( img ) ; } ; reader . readAsDataURL ( file ) ; } private saveAndClose ( ) { this . globalEmitter . emit ( Constants . setNoteTitleEvent , this . noteId , this . initialNoteTitle , this . noteTitle , async ( result ) => { const setTitleOperation = result . operation ; await this . setNoteTitleCallbackAsync ( result ) ; this . globalEmitter . emit ( Constants . setNoteTextEvent , this . noteId , this . quill . getText ( ) , this . getTasksCount ( ) , async ( operation ) => { const setTextOperation = operation ; await this . setNoteTextCallbackAsync ( operation ) ; if ( setTitleOperation === Operation . Success && setTextOperation === Operation . Success ) { this . logger . info ( `Closing note with id=${ this . noteId } after saving changes.` , 'NoteComponent' , 'saveAndClose' ) ; this . cleanup ( ) ; const window = remote . getCurrentWindow ( ) ; window . close ( ) ; } } ) ; } ) ; } private getNoteDetailsCallback ( result ) { this . zone . run ( ( ) => { this . initialNoteTitle = result . noteTitle ; this . noteTitle = result . noteTitle ; this . isMarked = result . isMarked ; this . setWindowTitle ( result . noteTitle ) ; } ) ; } private setEditorFontSize ( ) { document . body . setAttribute ( 'editor-font-size' , this . settings . fontSizeInNotes . toString ( ) ) ; } private setWindowTitle ( noteTitle ) { const window = remote . getCurrentWindow ( ) ; window . setTitle ( `${ ProductInformation . applicationName } - ${ noteTitle }` ) ; } private noteMarkChangedHandler ( noteId , isMarked ) { if ( this . noteId === noteId ) { this . zone . run ( ( ) => ( this . isMarked = isMarked ) ) ; } } private focusNoteHandler ( noteId ) { if ( this . noteId === noteId ) { const window = remote . getCurrentWindow ( ) ; if ( window . isMinimized ( ) ) { window . minimize ( ) ; window . restore ( ) ; } window . focus ( ) ; } } private closeNoteHandler ( noteId ) { if ( this . noteId === noteId ) { const window = remote . getCurrentWindow ( ) ; window . close ( ) ; } } private languageChangedHandler ( noteId ) { this . addContextMenuAsync ( ) ; } private fontSizeChangedHandler ( ) { this . setEditorFontSize ( ) ; } public clearSearch ( ) { const window = remote . getCurrentWindow ( ) ; window . webContents . stopFindInPage ( 'keepSelection' ) ; } private applySearch ( ) { this . globalEmitter . emit ( Constants . getSearchTextEvent , this . getSearchTextCallback . bind ( this ) ) ; } private getSearchTextCallback ( searchText ) { const window = remote . getCurrentWindow ( ) ; if ( searchText && searchText . length > 0 ) { const searchTextPieces = searchText . trim ( ) . split ( ' ' ) ; window . webContents . findInPage ( searchTextPieces [ 0 ] ) ; } } private async setNoteTitleCallbackAsync ( result ) { if ( result . operation === Operation . Blank ) { this . zone . run ( ( ) => ( this . noteTitle = this . initialNoteTitle ) ) ; this . snackBar . noteTitleCannotBeEmptyAsync ( ) ; } else if ( result . operation === Operation . Error ) { this . zone . run ( ( ) => ( this . noteTitle = this . initialNoteTitle ) ) ; const errorText = await this . translator . getAsync ( 'ErrorTexts.RenameNoteError' , { noteTitle : this . initialNoteTitle } ) ; this . zone . run ( ( ) => { this . dialog . open ( ErrorDialogComponent , { width : '450px' , data : { errorText : errorText } , } ) ; } ) ; } else if ( result . operation === Operation . Success ) { this . zone . run ( ( ) => { this . initialNoteTitle = result . noteTitle ; this . noteTitle = result . noteTitle ; this . setWindowTitle ( result . noteTitle ) ; } ) ; } else { } this . isTitleDirty = false ; } private writeTextToNoteFile ( ) { const activeCollection = this . settings . activeCollection ; const storageDirectory = this . settings . storageDirectory ; const jsonContent = JSON . stringify ( this . quill . getContents ( ) ) ; fs . writeFileSync ( path . join ( Utils . collectionToPath ( storageDirectory , activeCollection ) , `${ this . noteId }${ Constants . noteContentExtension }` ) , jsonContent ) ; } private async setNoteTextCallbackAsync ( operation ) { let showErrorDialog = false ; if ( operation === Operation . Success ) { try { this . writeTextToNoteFile ( ) ; } catch ( error ) { this . logger . error ( `Could not set text for the note with id='${ this . noteId }' in the note file. Cause: ${ error }` , 'NoteComponent' , 'setNoteTextCallbackAsync' ) ; showErrorDialog = true ; } } else if ( operation === Operation . Error ) { showErrorDialog = true ; } else { } if ( showErrorDialog ) { const errorText = await this . translator . getAsync ( 'ErrorTexts.UpdateNoteContentError' ) ; this . zone . run ( ( ) => { this . dialog . open ( ErrorDialogComponent , { width : '450px' , data : { errorText : errorText } , } ) ; } ) ; } this . isTextDirty = false ; } private async getNoteDetailsAsync ( ) { while ( ! this . noteTitle ) { await Utils . sleep ( 50 ) ; this . globalEmitter . emit ( Constants . getNoteDetailsEvent , this . noteId , this . getNoteDetailsCallback . bind ( this ) ) ; } try { const activeCollection = this . settings . activeCollection ; const storageDirectory = this . settings . storageDirectory ; const activeCollectionDirectory = Utils . collectionToPath ( storageDirectory , activeCollection ) ; const noteContentFileName = `${ this . noteId }${ Constants . noteContentExtension }` ; const noteContentFileFullPath = path . join ( activeCollectionDirectory , noteContentFileName ) ; const noteContent = await fs . readFile ( noteContentFileFullPath , 'utf8' ) ; if ( noteContent ) { this . logger . info ( `Setting the content for the note with id='${ this . noteId }'` , 'NoteComponent' , 'getNoteDetailsAsync' ) ; this . quill . setContents ( JSON . parse ( noteContent ) , 'silent' ) ; this . quill . history . clear ( ) ; } else { this . logger . error ( `Could not get the content for the note with id='${ this . noteId }'` , 'NoteComponent' , 'getNoteDetailsAsync' ) ; } } catch ( error ) { this . logger . error ( `Could not get the content for the note with id='${ this . noteId }'. Cause: ${ error }` , 'NoteComponent' , 'getNoteDetailsAsync' ) ; const errorText = await this . translator . getAsync ( 'ErrorTexts.GetNoteContentError' ) ; this . dialog . open ( ErrorDialogComponent , { width : '450px' , data : { errorText : errorText } , } ) ; } } private hideActionButtons ( ) { this . canPerformActions = false ; this . rotateActionsButton ( ) ; } private async hideActionButtonsDelayedAsync ( ) { await Utils . sleep ( 500 ) ; this . canPerformActions = false ; this . rotateActionsButton ( ) ; } public heading1 ( event ) { this . applyHeading ( 1 ) ; } public heading2 ( event ) { this . applyHeading ( 2 ) ; } private applyHeading ( headingSize ) { const range = this . quill . getSelection ( ) ; const format = this . quill . getFormat ( range . index , range . length ) ; const formatString = JSON . stringify ( format ) ; const selectionContainsHeader = ! formatString . includes ( `"header":${ headingSize }` ) ; if ( selectionContainsHeader ) { this . quill . format ( 'header' , headingSize ) ; } else { this . quill . removeFormat ( range . index , range . length ) ; } } public strikeThrough ( event ) { const range = this . quill . getSelection ( ) ; const format = this . quill . getFormat ( range . index , range . length ) ; const formatString = JSON . stringify ( format ) ; const applyStrikeThrough = ! formatString . includes ( 'strike' ) ; this . quill . formatText ( range . index , range . length , 'strike' , applyStrikeThrough ) ; } private getTasksCount ( ) { const noteContent = JSON . stringify ( this . quill . getContents ( ) ) ; const openTasksCount = ( noteContent . match ( /"list":"unchecked"/g ) || [ ] ) . length ; const closedTasksCount = ( noteContent . match ( /"list":"checked"/g ) || [ ] ) . length ; return new TasksCount ( openTasksCount , closedTasksCount ) ; } }