2018-03-09 17:49:35 +00:00
const BaseModel = require ( "lib/BaseModel.js" ) ;
const { Database } = require ( "lib/database.js" ) ;
const { Logger } = require ( "lib/logger.js" ) ;
const SyncTargetRegistry = require ( "lib/SyncTargetRegistry.js" ) ;
const { time } = require ( "lib/time-utils.js" ) ;
const { sprintf } = require ( "sprintf-js" ) ;
const ObjectUtils = require ( "lib/ObjectUtils" ) ;
const { toTitleCase } = require ( "lib/string-utils.js" ) ;
const { _ , supportedLocalesToLanguages , defaultLocale } = require ( "lib/locale.js" ) ;
2017-05-12 20:17:23 +00:00
class Setting extends BaseModel {
static tableName ( ) {
2018-03-09 17:49:35 +00:00
return "settings" ;
2017-05-12 20:17:23 +00:00
}
2017-07-03 20:50:45 +01:00
static modelType ( ) {
return BaseModel . TYPE _SETTING ;
2017-06-19 20:26:27 +01:00
}
2017-11-24 19:21:30 +00:00
static metadata ( ) {
if ( this . metadata _ ) return this . metadata _ ;
2018-02-22 18:58:15 +00:00
// A "public" setting means that it will show up in the various config screens (or config command for the CLI tool), however
// if if private a setting might still be handled and modified by the app. For instance, the settings related to sorting notes are not
// public for the mobile and desktop apps because they are handled separately in menus.
2017-11-24 19:21:30 +00:00
this . metadata _ = {
2018-03-09 17:49:35 +00:00
activeFolderId : { value : "" , type : Setting . TYPE _STRING , public : false } ,
firstStart : { value : true , type : Setting . TYPE _BOOL , public : false } ,
editor : {
value : "" ,
type : Setting . TYPE _STRING ,
public : true ,
appTypes : [ "cli" ] ,
label : ( ) => _ ( "Text editor" ) ,
description : ( ) => _ ( "The editor that will be used to open a note. If none is provided it will try to auto-detect the default editor." ) ,
} ,
locale : {
value : defaultLocale ( ) ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
label : ( ) => _ ( "Language" ) ,
options : ( ) => {
return ObjectUtils . sortByValue ( supportedLocalesToLanguages ( ) ) ;
} ,
} ,
dateFormat : {
value : Setting . DATE _FORMAT _1 ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
label : ( ) => _ ( "Date format" ) ,
options : ( ) => {
let options = { } ;
const now = new Date ( "2017-01-30T12:00:00" ) . getTime ( ) ;
options [ Setting . DATE _FORMAT _1 ] = time . formatMsToLocal ( now , Setting . DATE _FORMAT _1 ) ;
options [ Setting . DATE _FORMAT _2 ] = time . formatMsToLocal ( now , Setting . DATE _FORMAT _2 ) ;
options [ Setting . DATE _FORMAT _3 ] = time . formatMsToLocal ( now , Setting . DATE _FORMAT _3 ) ;
options [ Setting . DATE _FORMAT _4 ] = time . formatMsToLocal ( now , Setting . DATE _FORMAT _4 ) ;
options [ Setting . DATE _FORMAT _5 ] = time . formatMsToLocal ( now , Setting . DATE _FORMAT _5 ) ;
return options ;
} ,
} ,
timeFormat : {
value : Setting . TIME _FORMAT _1 ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
label : ( ) => _ ( "Time format" ) ,
options : ( ) => {
let options = { } ;
const now = new Date ( "2017-01-30T20:30:00" ) . getTime ( ) ;
options [ Setting . TIME _FORMAT _1 ] = time . formatMsToLocal ( now , Setting . TIME _FORMAT _1 ) ;
options [ Setting . TIME _FORMAT _2 ] = time . formatMsToLocal ( now , Setting . TIME _FORMAT _2 ) ;
return options ;
} ,
} ,
theme : {
value : Setting . THEME _LIGHT ,
type : Setting . TYPE _INT ,
public : true ,
appTypes : [ "mobile" ] ,
isEnum : true ,
label : ( ) => _ ( "Theme" ) ,
options : ( ) => {
let output = { } ;
output [ Setting . THEME _LIGHT ] = _ ( "Light" ) ;
output [ Setting . THEME _DARK ] = _ ( "Dark" ) ;
return output ;
} ,
} ,
uncompletedTodosOnTop : { value : true , type : Setting . TYPE _BOOL , public : true , appTypes : [ "cli" ] , label : ( ) => _ ( "Uncompleted to-dos on top" ) } ,
"notes.sortOrder.field" : {
value : "user_updated_time" ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
appTypes : [ "cli" ] ,
label : ( ) => _ ( "Sort notes by" ) ,
options : ( ) => {
const Note = require ( "lib/models/Note" ) ;
const noteSortFields = [ "user_updated_time" , "user_created_time" , "title" ] ;
const options = { } ;
for ( let i = 0 ; i < noteSortFields . length ; i ++ ) {
options [ noteSortFields [ i ] ] = toTitleCase ( Note . fieldToLabel ( noteSortFields [ i ] ) ) ;
}
return options ;
} ,
} ,
"notes.sortOrder.reverse" : { value : true , type : Setting . TYPE _BOOL , public : true , label : ( ) => _ ( "Reverse sort order" ) , appTypes : [ "cli" ] } ,
trackLocation : { value : true , type : Setting . TYPE _BOOL , public : true , label : ( ) => _ ( "Save geo-location with notes" ) } ,
newTodoFocus : {
value : "title" ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
appTypes : [ "desktop" ] ,
label : ( ) => _ ( "When creating a new to-do:" ) ,
options : ( ) => {
return {
title : _ ( "Focus title" ) ,
body : _ ( "Focus body" ) ,
} ;
} ,
} ,
newNoteFocus : {
value : "body" ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
appTypes : [ "desktop" ] ,
label : ( ) => _ ( "When creating a new note:" ) ,
options : ( ) => {
return {
title : _ ( "Focus title" ) ,
body : _ ( "Focus body" ) ,
} ;
} ,
} ,
showTrayIcon : { value : true , type : Setting . TYPE _BOOL , public : true , appTypes : [ "desktop" ] , label : ( ) => _ ( "Show tray icon" ) } ,
"encryption.enabled" : { value : false , type : Setting . TYPE _BOOL , public : false } ,
"encryption.activeMasterKeyId" : { value : "" , type : Setting . TYPE _STRING , public : false } ,
"encryption.passwordCache" : { value : { } , type : Setting . TYPE _OBJECT , public : false } ,
"style.zoom" : { value : "100" , type : Setting . TYPE _INT , public : true , appTypes : [ "desktop" ] , label : ( ) => _ ( "Global zoom percentage" ) , minimum : "50" , maximum : "500" , step : "10" } ,
"style.editor.fontFamily" : {
value : "" ,
type : Setting . TYPE _STRING ,
public : true ,
appTypes : [ "desktop" ] ,
label : ( ) => _ ( "Editor font family" ) ,
description : ( ) => _ ( "The font name will not be checked. If incorrect or empty, it will default to a generic monospace font." ) ,
} ,
autoUpdateEnabled : { value : true , type : Setting . TYPE _BOOL , public : true , appTypes : [ "desktop" ] , label : ( ) => _ ( "Automatically update the application" ) } ,
"sync.interval" : {
value : 300 ,
type : Setting . TYPE _INT ,
isEnum : true ,
public : true ,
label : ( ) => _ ( "Synchronisation interval" ) ,
options : ( ) => {
return {
0 : _ ( "Disabled" ) ,
300 : _ ( "%d minutes" , 5 ) ,
600 : _ ( "%d minutes" , 10 ) ,
1800 : _ ( "%d minutes" , 30 ) ,
3600 : _ ( "%d hour" , 1 ) ,
43200 : _ ( "%d hours" , 12 ) ,
86400 : _ ( "%d hours" , 24 ) ,
} ;
} ,
} ,
noteVisiblePanes : { value : [ "editor" , "viewer" ] , type : Setting . TYPE _ARRAY , public : false , appTypes : [ "desktop" ] } ,
showAdvancedOptions : { value : false , type : Setting . TYPE _BOOL , public : true , appTypes : [ "mobile" ] , label : ( ) => _ ( "Show advanced options" ) } ,
"sync.target" : {
value : SyncTargetRegistry . nameToId ( "onedrive" ) ,
type : Setting . TYPE _INT ,
isEnum : true ,
public : true ,
label : ( ) => _ ( "Synchronisation target" ) ,
description : appType => {
return appType !== "cli" ? null : _ ( "The target to synchonise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below)." ) ;
} ,
options : ( ) => {
return SyncTargetRegistry . idAndLabelPlainObject ( ) ;
} ,
} ,
"sync.2.path" : {
value : "" ,
type : Setting . TYPE _STRING ,
show : settings => {
try {
return settings [ "sync.target" ] == SyncTargetRegistry . nameToId ( "filesystem" ) ;
} catch ( error ) {
return false ;
}
} ,
public : true ,
label : ( ) => _ ( "Directory to synchronise with (absolute path)" ) ,
description : appType => {
return appType !== "cli" ? null : _ ( "The path to synchronise with when file system synchronisation is enabled. See `sync.target`." ) ;
} ,
} ,
"sync.5.path" : {
value : "" ,
type : Setting . TYPE _STRING ,
show : settings => {
return settings [ "sync.target" ] == SyncTargetRegistry . nameToId ( "nextcloud" ) ;
} ,
public : true ,
label : ( ) => _ ( "Nextcloud WebDAV URL" ) ,
} ,
"sync.5.username" : {
value : "" ,
type : Setting . TYPE _STRING ,
show : settings => {
return settings [ "sync.target" ] == SyncTargetRegistry . nameToId ( "nextcloud" ) ;
} ,
public : true ,
label : ( ) => _ ( "Nextcloud username" ) ,
} ,
"sync.5.password" : {
value : "" ,
type : Setting . TYPE _STRING ,
show : settings => {
return settings [ "sync.target" ] == SyncTargetRegistry . nameToId ( "nextcloud" ) ;
} ,
public : true ,
label : ( ) => _ ( "Nextcloud password" ) ,
secure : true ,
} ,
"sync.6.path" : {
value : "" ,
type : Setting . TYPE _STRING ,
show : settings => {
return settings [ "sync.target" ] == SyncTargetRegistry . nameToId ( "webdav" ) ;
} ,
public : true ,
label : ( ) => _ ( "WebDAV URL" ) ,
} ,
"sync.6.username" : {
value : "" ,
type : Setting . TYPE _STRING ,
show : settings => {
return settings [ "sync.target" ] == SyncTargetRegistry . nameToId ( "webdav" ) ;
} ,
public : true ,
label : ( ) => _ ( "WebDAV username" ) ,
} ,
"sync.6.password" : {
value : "" ,
type : Setting . TYPE _STRING ,
show : settings => {
return settings [ "sync.target" ] == SyncTargetRegistry . nameToId ( "webdav" ) ;
} ,
public : true ,
label : ( ) => _ ( "WebDAV password" ) ,
secure : true ,
} ,
"sync.3.auth" : { value : "" , type : Setting . TYPE _STRING , public : false } ,
"sync.4.auth" : { value : "" , type : Setting . TYPE _STRING , public : false } ,
"sync.1.context" : { value : "" , type : Setting . TYPE _STRING , public : false } ,
"sync.2.context" : { value : "" , type : Setting . TYPE _STRING , public : false } ,
"sync.3.context" : { value : "" , type : Setting . TYPE _STRING , public : false } ,
"sync.4.context" : { value : "" , type : Setting . TYPE _STRING , public : false } ,
"sync.5.context" : { value : "" , type : Setting . TYPE _STRING , public : false } ,
"sync.6.context" : { value : "" , type : Setting . TYPE _STRING , public : false } ,
2017-11-24 19:21:30 +00:00
} ;
return this . metadata _ ;
}
2017-07-24 18:58:11 +00:00
static settingMetadata ( key ) {
2017-11-24 19:21:30 +00:00
const metadata = this . metadata ( ) ;
2018-03-09 17:49:35 +00:00
if ( ! ( key in metadata ) ) throw new Error ( "Unknown key: " + key ) ;
2017-11-24 19:21:30 +00:00
let output = Object . assign ( { } , metadata [ key ] ) ;
2017-05-12 20:17:23 +00:00
output . key = key ;
return output ;
}
2017-10-30 00:29:10 +00:00
static keyExists ( key ) {
2017-11-24 19:21:30 +00:00
return key in this . metadata ( ) ;
2017-10-30 00:29:10 +00:00
}
2018-03-02 18:16:48 +00:00
static keyDescription ( key , appType = null ) {
const md = this . settingMetadata ( key ) ;
if ( ! md . description ) return null ;
return md . description ( appType ) ;
}
2017-08-22 19:57:35 +02:00
static keys ( publicOnly = false , appType = null ) {
if ( ! this . keys _ ) {
2017-11-24 19:21:30 +00:00
const metadata = this . metadata ( ) ;
2017-08-22 19:57:35 +02:00
this . keys _ = [ ] ;
2017-11-24 19:21:30 +00:00
for ( let n in metadata ) {
if ( ! metadata . hasOwnProperty ( n ) ) continue ;
2017-08-22 19:57:35 +02:00
this . keys _ . push ( n ) ;
}
2017-05-16 23:46:21 +02:00
}
2017-08-22 19:57:35 +02:00
if ( appType || publicOnly ) {
let output = [ ] ;
for ( let i = 0 ; i < this . keys _ . length ; i ++ ) {
const md = this . settingMetadata ( this . keys _ [ i ] ) ;
if ( publicOnly && ! md . public ) continue ;
if ( appType && md . appTypes && md . appTypes . indexOf ( appType ) < 0 ) continue ;
output . push ( md . key ) ;
}
return output ;
} else {
return this . keys _ ;
2017-06-27 20:16:03 +00:00
}
}
2017-07-31 22:51:24 +02:00
static isPublic ( key ) {
2017-08-22 19:57:35 +02:00
return this . keys ( true ) . indexOf ( key ) >= 0 ;
2017-07-31 22:51:24 +02:00
}
2017-05-12 20:17:23 +00:00
static load ( ) {
2017-07-26 17:49:01 +00:00
this . cancelScheduleSave ( ) ;
2017-05-12 20:17:23 +00:00
this . cache _ = [ ] ;
2018-03-09 17:49:35 +00:00
return this . modelSelectAll ( "SELECT * FROM settings" ) . then ( rows => {
2017-07-26 19:36:16 +01:00
this . cache _ = [ ] ;
2017-07-24 21:36:49 +01:00
2017-07-26 19:36:16 +01:00
for ( let i = 0 ; i < rows . length ; i ++ ) {
let c = rows [ i ] ;
2017-10-30 00:29:10 +00:00
if ( ! this . keyExists ( c . key ) ) continue ;
2017-07-25 22:55:26 +01:00
c . value = this . formatValue ( c . key , c . value ) ;
2017-07-26 19:36:16 +01:00
this . cache _ . push ( c ) ;
2017-07-24 21:36:49 +01:00
}
2017-07-26 17:49:01 +00:00
2017-10-21 17:53:43 +01:00
this . dispatchUpdateAll ( ) ;
} ) ;
}
2017-11-23 23:10:55 +00:00
static toPlainObject ( ) {
2017-10-21 17:53:43 +01:00
const keys = this . keys ( ) ;
let keyToValues = { } ;
for ( let i = 0 ; i < keys . length ; i ++ ) {
keyToValues [ keys [ i ] ] = this . value ( keys [ i ] ) ;
}
2017-11-23 23:10:55 +00:00
return keyToValues ;
}
2017-07-26 17:49:01 +00:00
2017-11-23 23:10:55 +00:00
static dispatchUpdateAll ( ) {
2017-10-21 17:53:43 +01:00
this . dispatch ( {
2018-03-09 17:49:35 +00:00
type : "SETTING_UPDATE_ALL" ,
2017-11-23 23:10:55 +00:00
settings : this . toPlainObject ( ) ,
2017-05-12 20:17:23 +00:00
} ) ;
}
2017-06-24 19:51:43 +01:00
static setConstant ( key , value ) {
2018-03-09 17:49:35 +00:00
if ( ! ( key in this . constants _ ) ) throw new Error ( "Unknown constant key: " + key ) ;
2017-06-24 19:51:43 +01:00
this . constants _ [ key ] = value ;
}
2017-05-12 20:17:23 +00:00
static setValue ( key , value ) {
2018-03-09 17:49:35 +00:00
if ( ! this . cache _ ) throw new Error ( "Settings have not been initialized!" ) ;
2017-07-31 22:51:24 +02:00
value = this . formatValue ( key , value ) ;
2018-03-09 17:49:35 +00:00
2017-05-12 20:17:23 +00:00
for ( let i = 0 ; i < this . cache _ . length ; i ++ ) {
2017-07-24 18:58:11 +00:00
let c = this . cache _ [ i ] ;
if ( c . key == key ) {
const md = this . settingMetadata ( key ) ;
2017-07-25 22:55:26 +01:00
if ( md . isEnum === true ) {
2017-07-24 18:58:11 +00:00
if ( ! this . isAllowedEnumOption ( key , value ) ) {
throw new Error ( _ ( 'Invalid option value: "%s". Possible values are: %s.' , value , this . enumOptionsDoc ( key ) ) ) ;
}
}
if ( c . value === value ) return ;
2017-07-25 22:55:26 +01:00
2018-01-17 21:17:40 +00:00
// Don't log this to prevent sensitive info (passwords, auth tokens...) to end up in logs
// this.logger().info('Setting: ' + key + ' = ' + c.value + ' => ' + value);
2017-07-25 22:55:26 +01:00
2017-11-14 18:02:58 +00:00
c . value = value ;
2017-07-26 17:49:01 +00:00
this . dispatch ( {
2018-03-09 17:49:35 +00:00
type : "SETTING_UPDATE_ONE" ,
2017-07-26 17:49:01 +00:00
key : key ,
value : c . value ,
} ) ;
this . scheduleSave ( ) ;
2017-05-12 20:17:23 +00:00
return ;
}
}
2017-07-25 22:55:26 +01:00
this . cache _ . push ( {
key : key ,
value : this . formatValue ( key , value ) ,
} ) ;
2017-07-26 17:49:01 +00:00
this . dispatch ( {
2018-03-09 17:49:35 +00:00
type : "SETTING_UPDATE_ONE" ,
2017-07-26 17:49:01 +00:00
key : key ,
value : this . formatValue ( key , value ) ,
} ) ;
this . scheduleSave ( ) ;
}
2017-12-17 20:51:45 +01:00
static setObjectKey ( settingKey , objectKey , value ) {
2017-12-28 19:57:21 +00:00
let o = this . value ( settingKey ) ;
2018-03-09 17:49:35 +00:00
if ( typeof o !== "object" ) o = { } ;
2017-12-17 20:51:45 +01:00
o [ objectKey ] = value ;
this . setValue ( settingKey , o ) ;
}
static deleteObjectKey ( settingKey , objectKey ) {
const o = this . value ( settingKey ) ;
2018-03-09 17:49:35 +00:00
if ( typeof o !== "object" ) return ;
2017-12-17 20:51:45 +01:00
delete o [ objectKey ] ;
this . setValue ( settingKey , o ) ;
}
2017-07-26 17:49:01 +00:00
static valueToString ( key , value ) {
const md = this . settingMetadata ( key ) ;
value = this . formatValue ( key , value ) ;
if ( md . type == Setting . TYPE _INT ) return value . toFixed ( 0 ) ;
2018-03-09 17:49:35 +00:00
if ( md . type == Setting . TYPE _BOOL ) return value ? "1" : "0" ;
if ( md . type == Setting . TYPE _ARRAY ) return value ? JSON . stringify ( value ) : "[]" ;
if ( md . type == Setting . TYPE _OBJECT ) return value ? JSON . stringify ( value ) : "{}" ;
if ( md . type == Setting . TYPE _STRING ) return value ? value + "" : "" ;
2018-01-07 19:20:10 +00:00
2018-03-09 17:49:35 +00:00
throw new Error ( "Unhandled value type: " + md . type ) ;
2017-05-12 20:17:23 +00:00
}
2017-07-25 22:55:26 +01:00
static formatValue ( key , value ) {
const md = this . settingMetadata ( key ) ;
2017-11-12 17:02:20 +00:00
2018-01-07 19:29:57 +00:00
if ( md . type == Setting . TYPE _INT ) return ! value ? 0 : Math . floor ( Number ( value ) ) ;
2017-11-12 17:02:20 +00:00
2017-07-26 17:49:01 +00:00
if ( md . type == Setting . TYPE _BOOL ) {
2018-03-09 17:49:35 +00:00
if ( typeof value === "string" ) {
2017-07-26 22:07:27 +01:00
value = value . toLowerCase ( ) ;
2018-03-09 17:49:35 +00:00
if ( value === "true" ) return true ;
if ( value === "false" ) return false ;
2017-07-26 22:07:27 +01:00
value = Number ( value ) ;
}
2017-07-26 17:49:01 +00:00
return ! ! value ;
}
2017-11-12 17:02:20 +00:00
if ( md . type === Setting . TYPE _ARRAY ) {
2017-11-14 18:02:58 +00:00
if ( ! value ) return [ ] ;
2017-11-12 17:02:20 +00:00
if ( Array . isArray ( value ) ) return value ;
2018-03-09 17:49:35 +00:00
if ( typeof value === "string" ) return JSON . parse ( value ) ;
2017-11-12 17:02:20 +00:00
return [ ] ;
}
2017-11-14 18:02:58 +00:00
if ( md . type === Setting . TYPE _OBJECT ) {
if ( ! value ) return { } ;
2018-03-09 17:49:35 +00:00
if ( typeof value === "object" ) return value ;
if ( typeof value === "string" ) return JSON . parse ( value ) ;
2017-11-14 18:02:58 +00:00
return { } ;
}
2018-01-07 19:20:10 +00:00
if ( md . type === Setting . TYPE _STRING ) {
2018-03-09 17:49:35 +00:00
if ( ! value ) return "" ;
return value + "" ;
2018-01-07 19:20:10 +00:00
}
2018-03-09 17:49:35 +00:00
throw new Error ( "Unhandled value type: " + md . type ) ;
2017-07-25 18:57:06 +00:00
}
2017-05-12 20:17:23 +00:00
static value ( key ) {
2017-12-14 19:39:13 +00:00
// Need to copy arrays and objects since in setValue(), the old value and new one is compared
// with strict equality and the value is updated only if changed. However if the caller acquire
// and object and change a key, the objects will be detected as equal. By returning a copy
// we avoid this problem.
function copyIfNeeded ( value ) {
2018-01-07 19:20:10 +00:00
if ( value === null || value === undefined ) return value ;
2017-12-14 19:39:13 +00:00
if ( Array . isArray ( value ) ) return value . slice ( ) ;
2018-03-09 17:49:35 +00:00
if ( typeof value === "object" ) return Object . assign ( { } , value ) ;
2017-12-14 19:39:13 +00:00
return value ;
}
2017-07-06 23:15:31 +01:00
if ( key in this . constants _ ) {
2017-11-19 22:08:58 +00:00
const v = this . constants _ [ key ] ;
2018-03-09 17:49:35 +00:00
const output = typeof v === "function" ? v ( ) : v ;
if ( output == "SET_ME" ) throw new Error ( "Setting constant has not been set: " + key ) ;
2017-07-06 23:15:31 +01:00
return output ;
}
2017-06-24 19:51:43 +01:00
2018-03-09 17:49:35 +00:00
if ( ! this . cache _ ) throw new Error ( "Settings have not been initialized!" ) ;
2017-06-19 18:58:49 +00:00
2017-05-12 20:17:23 +00:00
for ( let i = 0 ; i < this . cache _ . length ; i ++ ) {
if ( this . cache _ [ i ] . key == key ) {
2017-12-14 19:39:13 +00:00
return copyIfNeeded ( this . cache _ [ i ] . value ) ;
2017-05-12 20:17:23 +00:00
}
}
2017-07-25 22:55:26 +01:00
const md = this . settingMetadata ( key ) ;
2017-12-14 19:39:13 +00:00
return copyIfNeeded ( md . value ) ;
2017-05-12 20:17:23 +00:00
}
2017-07-24 18:58:11 +00:00
static isEnum ( key ) {
const md = this . settingMetadata ( key ) ;
2017-07-25 22:55:26 +01:00
return md . isEnum === true ;
2017-07-24 18:58:11 +00:00
}
static enumOptionValues ( key ) {
const options = this . enumOptions ( key ) ;
let output = [ ] ;
for ( let n in options ) {
if ( ! options . hasOwnProperty ( n ) ) continue ;
output . push ( n ) ;
}
return output ;
}
static enumOptionLabel ( key , value ) {
const options = this . enumOptions ( key ) ;
for ( let n in options ) {
if ( n == value ) return options [ n ] ;
}
2018-03-09 17:49:35 +00:00
return "" ;
2017-07-24 18:58:11 +00:00
}
static enumOptions ( key ) {
2017-11-24 19:21:30 +00:00
const metadata = this . metadata ( ) ;
2018-03-09 17:49:35 +00:00
if ( ! metadata [ key ] ) throw new Error ( "Unknown key: " + key ) ;
if ( ! metadata [ key ] . options ) throw new Error ( "No options for: " + key ) ;
2017-11-24 19:21:30 +00:00
return metadata [ key ] . options ( ) ;
2017-07-24 18:58:11 +00:00
}
2017-10-19 23:02:13 +01:00
static enumOptionsDoc ( key , templateString = null ) {
2018-03-09 17:49:35 +00:00
if ( templateString === null ) templateString = "%s: %s" ;
2017-07-24 18:58:11 +00:00
const options = this . enumOptions ( key ) ;
let output = [ ] ;
for ( let n in options ) {
if ( ! options . hasOwnProperty ( n ) ) continue ;
2017-10-19 23:02:13 +01:00
output . push ( sprintf ( templateString , n , options [ n ] ) ) ;
2017-07-24 18:58:11 +00:00
}
2018-03-09 17:49:35 +00:00
return output . join ( ", " ) ;
2017-07-24 18:58:11 +00:00
}
static isAllowedEnumOption ( key , value ) {
const options = this . enumOptions ( key ) ;
return ! ! options [ value ] ;
}
2018-02-06 18:59:36 +00:00
// For example, if settings is:
// { sync.5.path: 'http://example', sync.5.username: 'testing' }
// and baseKey is 'sync.5', the function will return
// { path: 'http://example', username: 'testing' }
static subValues ( baseKey , settings ) {
2017-05-16 23:46:21 +02:00
let output = { } ;
2018-02-06 18:59:36 +00:00
for ( let key in settings ) {
if ( ! settings . hasOwnProperty ( key ) ) continue ;
if ( key . indexOf ( baseKey ) === 0 ) {
const subKey = key . substr ( baseKey . length + 1 ) ;
output [ subKey ] = settings [ key ] ;
2017-05-16 23:46:21 +02:00
}
}
return output ;
}
// Currently only supports objects with properties one level deep
2018-02-06 18:59:36 +00:00
// static object(key) {
// let output = {};
// let keys = this.keys();
// for (let i = 0; i < keys.length; i++) {
// let k = keys[i].split('.');
// if (k[0] == key) {
// output[k[1]] = this.value(keys[i]);
// }
// }
// return output;
// }
// Currently only supports objects with properties one level deep
// static setObject(key, object) {
// for (let n in object) {
// if (!object.hasOwnProperty(n)) continue;
// this.setValue(key + '.' + n, object[n]);
// }
// }
2017-05-16 23:46:21 +02:00
2018-02-15 17:12:09 +00:00
static async saveAll ( ) {
2017-07-26 17:49:01 +00:00
if ( ! this . saveTimeoutId _ ) return Promise . resolve ( ) ;
2017-05-19 19:12:09 +00:00
2018-03-09 17:49:35 +00:00
this . logger ( ) . info ( "Saving settings..." ) ;
2017-07-26 17:49:01 +00:00
clearTimeout ( this . saveTimeoutId _ ) ;
this . saveTimeoutId _ = null ;
2017-05-19 19:12:09 +00:00
2017-06-11 22:11:14 +01:00
let queries = [ ] ;
2018-03-09 17:49:35 +00:00
queries . push ( "DELETE FROM settings" ) ;
2017-06-11 22:11:14 +01:00
for ( let i = 0 ; i < this . cache _ . length ; i ++ ) {
2017-07-26 17:49:01 +00:00
let s = Object . assign ( { } , this . cache _ [ i ] ) ;
s . value = this . valueToString ( s . key , s . value ) ;
queries . push ( Database . insertQuery ( this . tableName ( ) , s ) ) ;
2017-06-11 22:11:14 +01:00
}
2018-02-15 17:12:09 +00:00
await BaseModel . db ( ) . transactionExecBatch ( queries ) ;
2018-03-09 17:49:35 +00:00
this . logger ( ) . info ( "Settings have been saved." ) ;
2017-05-19 19:12:09 +00:00
}
2017-07-26 17:49:01 +00:00
static scheduleSave ( ) {
2018-02-15 17:12:09 +00:00
if ( ! Setting . autoSaveEnabled ) return ;
2017-07-26 17:49:01 +00:00
if ( this . saveTimeoutId _ ) clearTimeout ( this . saveTimeoutId _ ) ;
2017-05-19 19:12:09 +00:00
2017-07-26 17:49:01 +00:00
this . saveTimeoutId _ = setTimeout ( ( ) => {
2017-05-19 19:12:09 +00:00
this . saveAll ( ) ;
2017-05-12 20:17:23 +00:00
} , 500 ) ;
}
2017-07-26 17:49:01 +00:00
static cancelScheduleSave ( ) {
if ( this . saveTimeoutId _ ) clearTimeout ( this . saveTimeoutId _ ) ;
this . saveTimeoutId _ = null ;
2017-06-19 20:26:27 +01:00
}
2017-07-23 19:26:50 +01:00
static publicSettings ( appType ) {
2018-03-09 17:49:35 +00:00
if ( ! appType ) throw new Error ( "appType is required" ) ;
2017-07-23 19:26:50 +01:00
2017-11-24 19:21:30 +00:00
const metadata = this . metadata ( ) ;
2017-07-23 19:26:50 +01:00
let output = { } ;
2017-11-24 19:21:30 +00:00
for ( let key in metadata ) {
if ( ! metadata . hasOwnProperty ( key ) ) continue ;
let s = Object . assign ( { } , metadata [ key ] ) ;
2017-07-23 19:26:50 +01:00
if ( ! s . public ) continue ;
if ( s . appTypes && s . appTypes . indexOf ( appType ) < 0 ) continue ;
s . value = this . value ( key ) ;
output [ key ] = s ;
}
return output ;
}
2017-10-19 23:02:13 +01:00
static typeToString ( typeId ) {
2018-03-09 17:49:35 +00:00
if ( typeId === Setting . TYPE _INT ) return "int" ;
if ( typeId === Setting . TYPE _STRING ) return "string" ;
if ( typeId === Setting . TYPE _BOOL ) return "bool" ;
if ( typeId === Setting . TYPE _ARRAY ) return "array" ;
if ( typeId === Setting . TYPE _OBJECT ) return "object" ;
2017-10-19 23:02:13 +01:00
}
2017-05-12 20:17:23 +00:00
}
2017-07-25 22:55:26 +01:00
Setting . TYPE _INT = 1 ;
Setting . TYPE _STRING = 2 ;
Setting . TYPE _BOOL = 3 ;
2017-11-12 17:02:20 +00:00
Setting . TYPE _ARRAY = 4 ;
2017-11-14 18:02:58 +00:00
Setting . TYPE _OBJECT = 5 ;
2017-07-25 22:55:26 +01:00
2017-07-31 22:51:24 +02:00
Setting . THEME _LIGHT = 1 ;
Setting . THEME _DARK = 2 ;
2018-03-09 17:49:35 +00:00
Setting . DATE _FORMAT _1 = "DD/MM/YYYY" ;
Setting . DATE _FORMAT _2 = "DD/MM/YY" ;
Setting . DATE _FORMAT _3 = "MM/DD/YYYY" ;
Setting . DATE _FORMAT _4 = "MM/DD/YY" ;
Setting . DATE _FORMAT _5 = "YYYY-MM-DD" ;
2017-11-28 18:02:54 +00:00
2018-03-09 17:49:35 +00:00
Setting . TIME _FORMAT _1 = "HH:mm" ;
Setting . TIME _FORMAT _2 = "h:mm A" ;
2017-11-28 18:02:54 +00:00
2017-06-24 19:51:43 +01:00
// Contains constants that are set by the application and
// cannot be modified by the user:
Setting . constants _ = {
2018-03-09 17:49:35 +00:00
env : "SET_ME" ,
2017-10-30 21:29:36 +00:00
isDemo : false ,
2018-03-09 17:49:35 +00:00
appName : "joplin" ,
appId : "SET_ME" , // Each app should set this identifier
appType : "SET_ME" , // 'cli' or 'mobile'
resourceDir : "" ,
profileDir : "" ,
tempDir : "" ,
2017-11-17 18:02:01 +00:00
openDevTools : false ,
2018-03-09 17:49:35 +00:00
} ;
2017-06-24 19:51:43 +01:00
2018-02-15 17:12:09 +00:00
Setting . autoSaveEnabled = true ;
2018-03-09 17:49:35 +00:00
module . exports = Setting ;