import angular from 'angular' ; import { Component , OnInit } from 'angular-ts-decorators' ; import autobind from 'autobind-decorator' ; import { AndroidAppHelperService } from '../../android/android-app/shared/android-app-helper/android-app-helper.service' ; import { AlertType } from '../../shared/alert/alert.enum' ; import { AlertService } from '../../shared/alert/alert.service' ; import { BookmarkChangeType } from '../../shared/bookmark/bookmark.enum' ; import { AddBookmarkChangeData , Bookmark , BookmarkChange , BookmarkMetadata , ModifyBookmarkChangeData , RemoveBookmarkChangeData } from '../../shared/bookmark/bookmark.interface' ; import { BookmarkHelperService } from '../../shared/bookmark/bookmark-helper/bookmark-helper.service' ; import { ExceptionHandler } from '../../shared/errors/errors.interface' ; import Globals from '../../shared/global-shared.constants' ; import { PlatformService , WebpageMetadata } from '../../shared/global-shared.interface' ; import { SyncType } from '../../shared/sync/sync.enum' ; import { UtilityService } from '../../shared/utility/utility.service' ; import { WorkingService } from '../../shared/working/working.service' ; import { KeyCode , RoutePath } from '../app.enum' ; import { AppHelperService } from '../shared/app-helper/app-helper.service' ; import { BookmarkRouteParams } from './app-bookmark.interface' ; @ autobind @ Component ( { controllerAs : 'vm' , selector : 'appBookmark' , styles : [ require ( './app-bookmark.component.scss' ) ] , template : require ( './app-bookmark.component.html' ) } ) export class AppBookmarkComponent implements OnInit { Strings = require ( '../../../../res/strings/en.json' ) ; $exceptionHandler : ExceptionHandler ; $q : ng . IQService ; $routeParams : ng . route . IRouteParamsService ; $timeout : ng . ITimeoutService ; alertSvc : AlertService ; appHelperSvc : AppHelperService ; bookmarkHelperSvc : BookmarkHelperService ; platformSvc : PlatformService ; utilitySvc : UtilityService ; workingSvc : WorkingService ; addButtonDisabledUntilEditForm = false ; RoutePath = RoutePath ; bookmarkForm : ng . IFormController ; bookmarkFormData : BookmarkMetadata ; currentBookmarkId : number ; defaultProtocol = 'https://' ; descriptionFieldOriginalHeight : string ; displayUpdatePropertiesButton = false ; editMode = false ; originalUrl : string ; tagLookahead : string ; tagText : string ; tagTextMeasure : string ; static $inject = [ '$exceptionHandler' , '$q' , '$routeParams' , '$scope' , '$timeout' , 'AlertService' , 'AppHelperService' , 'BookmarkHelperService' , 'PlatformService' , 'UtilityService' , 'WorkingService' ] ; constructor ( $exceptionHandler , $q , $routeParams , $scope , $timeout , AlertSvc , AppHelperSvc , BookmarkHelperSvc , PlatformSvc , UtilitySvc , WorkingSvc ) { this . $exceptionHandler = $exceptionHandler ; this . $q = $q ; this . $routeParams = $routeParams ; this . $timeout = $timeout ; this . alertSvc = AlertSvc ; this . appHelperSvc = AppHelperSvc ; this . bookmarkHelperSvc = BookmarkHelperSvc ; this . platformSvc = PlatformSvc ; this . utilitySvc = UtilitySvc ; this . workingSvc = WorkingSvc ; $scope . $watch ( ( ) => this . tagText , ( newVal , oldVal ) => { if ( newVal !== oldVal ) { this . tagsTextChanged ( ) ; } } ) ; } changesSynced ( ) { return this . appHelperSvc . syncBookmarksSuccess ( ) ; } clearExistingTags ( ) { this . bookmarkFormData . tags = [ ] ; this . bookmarkForm . $setDirty ( ) ; this . appHelperSvc . focusOnElement ( 'input[name="bookmarkTags"]' ) ; } close ( ) { this . appHelperSvc . switchView ( ) ; } createBookmark ( ) { if ( this . tagText ?. length ) { this . createTags ( ) ; } const bookmarkToAdd = this . bookmarkHelperSvc . cleanBookmark ( this . bookmarkFormData ) ; if ( ! new RegExp ( Globals . URL . ProtocolRegex ) . test ( bookmarkToAdd . url ?? '' ) ) { bookmarkToAdd . url = `${ this . defaultProtocol }${ bookmarkToAdd . url }` ; } return this . validateBookmark ( bookmarkToAdd ) . then ( ( isValid ) => { if ( ! isValid ) { this . bookmarkForm . bookmarkUrl . $setValidity ( 'Exists' , false ) ; return ; } this . workingSvc . show ( ) ; const data = { metadata : bookmarkToAdd } ; const changeInfo = { changeData : data , type : BookmarkChangeType . Add } ; return this . queueSync ( changeInfo ) . then ( ( ) => this . changesSynced ( ) ) ; } ) ; } createTags ( ) { this . platformSvc . getCurrentLocale ( ) . then ( ( currentLocale ) => { const newTags = this . utilitySvc . getTagArrayFromText ( this . tagText . toLocaleLowerCase ( currentLocale ) ) ; this . bookmarkFormData . tags = this . utilitySvc . sortWords ( [ ... newTags , ... ( this . bookmarkFormData . tags ?? [ ] ) ] ) ; this . bookmarkForm . $setDirty ( ) ; this . tagText = undefined ; this . tagLookahead = undefined ; this . appHelperSvc . focusOnElement ( 'input[name="bookmarkTags"]' ) ; } ) ; } deleteBookmark ( ) { this . workingSvc . show ( ) ; const data = { id : this . currentBookmarkId } ; const changeInfo = { changeData : data , type : BookmarkChangeType . Remove } ; return this . queueSync ( changeInfo ) . then ( this . changesSynced ) ; } descriptionChanged ( ) { this . $timeout ( ( ) => { this . bookmarkFormData . description = this . utilitySvc . trimToNearestWord ( this . bookmarkFormData . description , Globals . Bookmarks . DescriptionMaxLength ) ; } ) ; } getMetadataForCurrentPage ( ) { return this . platformSvc . getPageMetadata ( true ) . then ( this . getPageMetadataAsBookmarkMetadata ) ; } getMetadataForUrl ( url ) { return this . $q < BookmarkMetadata > ( ( resolve , reject ) => { if ( angular . isUndefined ( url ?? undefined ) ) { return resolve ( ) ; } return this . platformSvc . getPageMetadata ( true , url ) . then ( this . getPageMetadataAsBookmarkMetadata ) . then ( resolve ) . catch ( reject ) ; } ) ; } getPageMetadataAsBookmarkMetadata ( metadata ) { if ( angular . isUndefined ( metadata ?? undefined ) ) { return ; } return { description : this . utilitySvc . trimToNearestWord ( metadata . description , Globals . Bookmarks . DescriptionMaxLength ) , tags : this . utilitySvc . getTagArrayFromText ( metadata . tags ) , title : metadata . title , url : metadata . url } ; } ngOnInit ( ) { return ( this . $q < Bookmark > ( ( resolve ) => { const bookmarkId = ( this . $routeParams as BookmarkRouteParams ) . id ; if ( bookmarkId ) { this . editMode = true ; return this . bookmarkHelperSvc . getBookmarkById ( parseInt ( bookmarkId , 10 ) ) . then ( resolve ) ; } return this . bookmarkHelperSvc . findCurrentUrlInBookmarks ( ) . then ( ( existingBookmark ) => { if ( existingBookmark ) { return resolve ( existingBookmark ) ; } resolve ( ) ; } ) ; } ) . then ( ( bookmark ) => { if ( ! angular . isUndefined ( bookmark ?? undefined ) ) { this . bookmarkFormData = this . bookmarkHelperSvc . extractBookmarkMetadata ( bookmark ) ; this . currentBookmarkId = bookmark . id ; this . editMode = angular . isNumber ( this . currentBookmarkId ) ; this . originalUrl = this . bookmarkFormData . url ; return true ; } this . bookmarkFormData = { url : this . defaultProtocol } ; this . originalUrl = this . bookmarkFormData . url ; this . addButtonDisabledUntilEditForm = true ; return this . getMetadataForCurrentPage ( ) . then ( ( currentPageMetadata ) => { if ( currentPageMetadata === false ) { return false ; } if ( ! angular . isUndefined ( currentPageMetadata ) ) { this . bookmarkFormData = currentPageMetadata as BookmarkMetadata ; } return true ; } ) . catch ( this . $exceptionHandler ) . finally ( ( ) => { this . addButtonDisabledUntilEditForm = false ; } ) ; } ) . then ( ( setFocus ) => { if ( setFocus ) { this . $timeout ( ( ) => this . appHelperSvc . focusOnElement ( '.focused' ) ) ; } } ) . catch ( ( err ) => { if ( err . url ) { this . bookmarkFormData = { url : err . url } as BookmarkMetadata ; } throw err ; } ) ) ; } populateFromUrlMetadata ( ) { this . getMetadataForUrl ( this . bookmarkFormData . url ) . then ( ( metadata ) => { this . displayUpdatePropertiesButton = false ; if ( ! metadata ?. title && ! metadata ?. description && ! metadata ?. tags ) { return ; } this . bookmarkFormData . title = metadata . title ?? this . bookmarkFormData . title ; this . bookmarkFormData . description = metadata . description ?? this . bookmarkFormData . description ; this . bookmarkFormData . tags = metadata . tags ?? this . bookmarkFormData . tags ; this . bookmarkForm . bookmarkUrl . $setPristine ( ) ; this . alertSvc . setCurrentAlert ( { message : this . platformSvc . getI18nString ( this . Strings . Alert . GetMetadata . Success ) , type : AlertType . Information } ) ; } ) ; } queueSync ( changeInfo ) { return this . platformSvc . queueSync ( { changeInfo , type : SyncType . LocalAndRemote } ) . catch ( ( err ) => this . appHelperSvc . syncBookmarksFailed ( err ) . then ( ( ) => { throw err ; } ) ) ; } removeTag ( tag ) { this . bookmarkFormData . tags = this . bookmarkFormData . tags . filter ( ( x ) => x !== tag ) ; this . bookmarkForm . $setDirty ( ) ; this . appHelperSvc . focusOnElement ( '#bookmarkForm input[name="bookmarkTags"]' ) ; } selectTagsLookahead ( ) { this . tagText += this . tagLookahead . replace ( /&nbsp;/g , ' ' ) ; this . createTags ( ) ; this . appHelperSvc . focusOnElement ( 'input[name="bookmarkTags"]' ) ; } shareBookmark ( event , bookmarkToShare ) { this . utilitySvc . stopEventPropagation ( event ) ; ( this . appHelperSvc as AndroidAppHelperService ) . shareBookmark ( bookmarkToShare ) ; } tagsTextChanged ( ) { if ( ! this . tagText ?. trim ( ) ) { return ; } this . platformSvc . getCurrentLocale ( ) . then ( ( currentLocale ) => { const words = this . utilitySvc . splitTextIntoWords ( this . tagText , currentLocale ) ; let lastWord = words . slice ( - 1 ) . find ( Boolean ) ; if ( ! angular . isUndefined ( lastWord ) ) { lastWord = lastWord . trimLeft ( ) ; } if ( ! ( lastWord ?. length >= Globals . LookaheadMinChars ) ) { this . tagLookahead = undefined ; return ; } return this . bookmarkHelperSvc . getLookahead ( lastWord . toLocaleLowerCase ( currentLocale ) , null , true , this . bookmarkFormData . tags ) . then ( ( results ) => { if ( ! results ) { this . tagLookahead = undefined ; return ; } let lookahead = results [ 0 ] ; const word = results [ 1 ] ; if ( lookahead && this . utilitySvc . stringsAreEquivalent ( word , lastWord , currentLocale ) ) { lookahead = lookahead ? lookahead . substring ( word . length ) : undefined ; this . tagTextMeasure = this . tagText . replace ( /\s/g , '&nbsp;' ) ; this . tagLookahead = lookahead . replace ( /\s/g , '&nbsp;' ) ; } } ) ; } ) ; } tagsTextKeyDown ( event ) { switch ( event . keyCode ) { case KeyCode . Enter : event . preventDefault ( ) ; this . createTags ( ) ; break ; case KeyCode . Tab : case KeyCode . ArrowRight : if ( ! this . tagLookahead ) { break ; } event . preventDefault ( ) ; this . tagText += this . tagLookahead . replace ( /&nbsp;/g , ' ' ) ; this . tagsTextChanged ( ) ; this . appHelperSvc . focusOnElement ( 'input[name="bookmarkTags"]' ) ; break ; default : } } updateBookmark ( ) { if ( this . tagText ?. length ) { this . createTags ( ) ; } const bookmarkToModify = this . bookmarkHelperSvc . cleanBookmark ( this . bookmarkFormData ) ; bookmarkToModify . id = this . currentBookmarkId ; if ( ! new RegExp ( Globals . URL . ProtocolRegex ) . test ( bookmarkToModify . url ?? '' ) ) { bookmarkToModify . url = `${ this . defaultProtocol }${ bookmarkToModify . url }` ; } return this . validateBookmark ( bookmarkToModify , this . originalUrl ) . then ( ( isValid ) => { if ( ! isValid ) { this . bookmarkForm . bookmarkUrl . $setValidity ( 'Exists' , false ) ; return ; } this . workingSvc . show ( ) ; const data = { bookmark : bookmarkToModify } ; const changeInfo = { changeData : data , type : BookmarkChangeType . Modify } ; return this . queueSync ( changeInfo ) . then ( ( ) => { return this . changesSynced ( ) ; } ) ; } ) ; } urlChanged ( ) { if ( this . bookmarkForm . bookmarkUrl . $invalid ) { this . bookmarkForm . bookmarkUrl . $setValidity ( 'Exists' , true ) ; this . bookmarkForm . bookmarkUrl . $setValidity ( 'Invalid' , true ) ; this . displayUpdatePropertiesButton = false ; } if ( ( this . bookmarkFormData . url ?? undefined ) === undefined ) { return ; } if ( ! new RegExp ( `^${ Globals . URL . ValidUrlRegex }$` , 'i' ) . test ( this . bookmarkFormData . url ) ) { this . bookmarkForm . bookmarkUrl . $setValidity ( 'Invalid' , false ) ; return ; } this . displayUpdatePropertiesButton = this . bookmarkForm . bookmarkUrl . $dirty && ! this . bookmarkForm . bookmarkUrl . $invalid ; } validateBookmark ( bookmarkToValidate , originalUrl ? ) { return this . platformSvc . getCurrentLocale ( ) . then ( ( currentLocale ) => { if ( this . utilitySvc . stringsAreEquivalent ( bookmarkToValidate . url , originalUrl , currentLocale ) ) { return true ; } return this . bookmarkHelperSvc . searchBookmarks ( { url : bookmarkToValidate . url } ) . then ( ( results ) => { const duplicateBookmarks = results . filter ( ( b ) => { return this . utilitySvc . stringsAreEquivalent ( b . url , bookmarkToValidate . url , currentLocale ) ; } ) ; return duplicateBookmarks . length === 0 ; } ) ; } ) ; } }