2017-12-14 20:12:14 +02:00
const BaseModel = require ( 'lib/BaseModel.js' ) ;
2017-11-03 02:09:34 +02:00
const { Database } = require ( 'lib/database.js' ) ;
2017-11-24 21:21:30 +02:00
const SyncTargetRegistry = require ( 'lib/SyncTargetRegistry.js' ) ;
2017-11-28 20:02:54 +02:00
const { time } = require ( 'lib/time-utils.js' ) ;
2017-11-03 02:09:34 +02:00
const { sprintf } = require ( 'sprintf-js' ) ;
2018-02-21 21:58:28 +02:00
const ObjectUtils = require ( 'lib/ObjectUtils' ) ;
2018-02-22 20:58:15 +02:00
const { toTitleCase } = require ( 'lib/string-utils.js' ) ;
2020-04-10 19:33:09 +02:00
const { rtrimSlashes , toSystemSlashes } = require ( 'lib/path-utils.js' ) ;
2017-11-03 02:09:34 +02:00
const { _ , supportedLocalesToLanguages , defaultLocale } = require ( 'lib/locale.js' ) ;
2018-04-16 19:18:28 +02:00
const { shim } = require ( 'lib/shim' ) ;
2017-05-12 22:17:23 +02:00
class Setting extends BaseModel {
static tableName ( ) {
return 'settings' ;
}
2017-07-03 21:50:45 +02:00
static modelType ( ) {
return BaseModel . TYPE _SETTING ;
2017-06-19 21:26:27 +02:00
}
2017-11-24 21:21:30 +02:00
static metadata ( ) {
if ( this . metadata _ ) return this . metadata _ ;
2018-04-16 19:18:28 +02:00
const platform = shim . platformName ( ) ;
2019-09-17 22:32:00 +02:00
const mobilePlatform = shim . mobilePlatform ( ) ;
2018-04-16 19:18:28 +02:00
2020-04-10 20:16:18 +02:00
let wysiwygYes = '' ;
let wysiwygNo = '' ;
if ( shim . isElectron ( ) ) {
wysiwygYes = ` ${ _ ( '(wysiwyg: %s)' , _ ( 'yes' ) ) } ` ;
wysiwygNo = ` ${ _ ( '(wysiwyg: %s)' , _ ( 'no' ) ) } ` ;
}
2019-04-18 15:59:17 +02:00
const emptyDirWarning = _ ( 'Attention: If you change this location, make sure you copy all your content to it before syncing, otherwise all files will be removed! See the FAQ for more details: %s' , 'https://joplinapp.org/faq/' ) ;
2019-03-29 10:01:58 +02:00
2018-02-22 20:58:15 +02: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 21:21:30 +02:00
this . metadata _ = {
2019-10-10 23:23:11 +02:00
'clientId' : {
value : '' ,
type : Setting . TYPE _STRING ,
public : false ,
} ,
2019-11-06 23:51:08 +02:00
'editor.keyboardMode' : {
value : 'default' ,
type : Setting . TYPE _STRING ,
public : true ,
appTypes : [ 'desktop' ] ,
isEnum : true ,
label : ( ) => _ ( 'Keyboard Mode' ) ,
options : ( ) => {
2020-03-14 01:46:14 +02:00
const output = { } ;
2019-11-06 23:51:08 +02:00
output [ 'default' ] = _ ( 'Default' ) ;
output [ 'emacs' ] = _ ( 'Emacs' ) ;
output [ 'vim' ] = _ ( 'Vim' ) ;
return output ;
} ,
} ,
2019-07-29 15:43:53 +02:00
'sync.target' : {
value : SyncTargetRegistry . nameToId ( 'dropbox' ) ,
type : Setting . TYPE _INT ,
isEnum : true ,
public : true ,
section : 'sync' ,
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 : ( ) => {
2020-02-08 13:59:19 +02:00
return SyncTargetRegistry . idAndLabelPlainObject ( platform ) ;
2019-07-29 15:43:53 +02:00
} ,
} ,
'sync.2.path' : {
value : '' ,
type : Setting . TYPE _STRING ,
section : 'sync' ,
show : settings => {
try {
return settings [ 'sync.target' ] == SyncTargetRegistry . nameToId ( 'filesystem' ) ;
} catch ( error ) {
return false ;
}
} ,
filter : value => {
return value ? rtrimSlashes ( value ) : '' ;
} ,
public : true ,
label : ( ) => _ ( 'Directory to synchronise with (absolute path)' ) ,
description : ( ) => emptyDirWarning ,
} ,
'sync.5.path' : {
value : '' ,
type : Setting . TYPE _STRING ,
section : 'sync' ,
show : settings => {
return settings [ 'sync.target' ] == SyncTargetRegistry . nameToId ( 'nextcloud' ) ;
} ,
public : true ,
label : ( ) => _ ( 'Nextcloud WebDAV URL' ) ,
description : ( ) => emptyDirWarning ,
} ,
'sync.5.username' : {
value : '' ,
type : Setting . TYPE _STRING ,
section : 'sync' ,
show : settings => {
return settings [ 'sync.target' ] == SyncTargetRegistry . nameToId ( 'nextcloud' ) ;
} ,
public : true ,
label : ( ) => _ ( 'Nextcloud username' ) ,
} ,
'sync.5.password' : {
value : '' ,
type : Setting . TYPE _STRING ,
section : 'sync' ,
show : settings => {
return settings [ 'sync.target' ] == SyncTargetRegistry . nameToId ( 'nextcloud' ) ;
} ,
public : true ,
label : ( ) => _ ( 'Nextcloud password' ) ,
secure : true ,
} ,
'sync.6.path' : {
value : '' ,
type : Setting . TYPE _STRING ,
section : 'sync' ,
show : settings => {
return settings [ 'sync.target' ] == SyncTargetRegistry . nameToId ( 'webdav' ) ;
} ,
public : true ,
label : ( ) => _ ( 'WebDAV URL' ) ,
description : ( ) => emptyDirWarning ,
} ,
'sync.6.username' : {
value : '' ,
type : Setting . TYPE _STRING ,
section : 'sync' ,
show : settings => {
return settings [ 'sync.target' ] == SyncTargetRegistry . nameToId ( 'webdav' ) ;
} ,
public : true ,
label : ( ) => _ ( 'WebDAV username' ) ,
} ,
'sync.6.password' : {
value : '' ,
type : Setting . TYPE _STRING ,
section : 'sync' ,
show : settings => {
return settings [ 'sync.target' ] == SyncTargetRegistry . nameToId ( 'webdav' ) ;
} ,
public : true ,
label : ( ) => _ ( 'WebDAV password' ) ,
secure : true ,
} ,
2019-06-08 01:23:17 +02:00
'sync.3.auth' : { value : '' , type : Setting . TYPE _STRING , public : false } ,
'sync.4.auth' : { value : '' , type : Setting . TYPE _STRING , public : false } ,
'sync.7.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 } ,
'sync.7.context' : { value : '' , type : Setting . TYPE _STRING , public : false } ,
2019-12-13 03:16:34 +02:00
'sync.5.syncTargets' : { value : { } , type : Setting . TYPE _OBJECT , public : false } ,
2019-07-29 15:43:53 +02:00
'sync.resourceDownloadMode' : {
value : 'always' ,
type : Setting . TYPE _STRING ,
section : 'sync' ,
public : true ,
2019-12-13 03:16:34 +02:00
advanced : true ,
2019-07-29 15:43:53 +02:00
isEnum : true ,
appTypes : [ 'mobile' , 'desktop' ] ,
label : ( ) => _ ( 'Attachment download behaviour' ) ,
description : ( ) => _ ( 'In "Manual" mode, attachments are downloaded only when you click on them. In "Auto", they are downloaded when you open the note. In "Always", all the attachments are downloaded whether you open the note or not.' ) ,
options : ( ) => {
return {
always : _ ( 'Always' ) ,
manual : _ ( 'Manual' ) ,
auto : _ ( 'Auto' ) ,
} ;
} ,
} ,
2019-12-13 03:16:34 +02:00
'sync.maxConcurrentConnections' : { value : 5 , type : Setting . TYPE _INT , public : true , advanced : true , section : 'sync' , label : ( ) => _ ( 'Max concurrent connections' ) , minimum : 1 , maximum : 20 , step : 1 } ,
2019-07-29 15:43:53 +02:00
activeFolderId : { value : '' , type : Setting . TYPE _STRING , public : false } ,
firstStart : { value : true , type : Setting . TYPE _BOOL , public : false } ,
locale : {
value : defaultLocale ( ) ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
label : ( ) => _ ( 'Language' ) ,
options : ( ) => {
return ObjectUtils . sortByValue ( supportedLocalesToLanguages ( { includeStats : true } ) ) ;
} ,
} ,
dateFormat : {
value : Setting . DATE _FORMAT _1 ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
label : ( ) => _ ( 'Date format' ) ,
options : ( ) => {
2020-03-14 01:46:14 +02:00
const options = { } ;
2019-07-29 15:43:53 +02:00
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 ) ;
options [ Setting . DATE _FORMAT _6 ] = time . formatMsToLocal ( now , Setting . DATE _FORMAT _6 ) ;
2020-01-26 19:26:50 +02:00
options [ Setting . DATE _FORMAT _7 ] = time . formatMsToLocal ( now , Setting . DATE _FORMAT _7 ) ;
2019-07-29 15:43:53 +02:00
return options ;
} ,
} ,
timeFormat : {
value : Setting . TIME _FORMAT _1 ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
label : ( ) => _ ( 'Time format' ) ,
options : ( ) => {
2020-03-14 01:46:14 +02:00
const options = { } ;
2019-07-29 15:43:53 +02:00
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' , 'desktop' ] ,
isEnum : true ,
label : ( ) => _ ( 'Theme' ) ,
section : 'appearance' ,
options : ( ) => {
2020-03-14 01:46:14 +02:00
const output = { } ;
2019-07-29 15:43:53 +02:00
output [ Setting . THEME _LIGHT ] = _ ( 'Light' ) ;
output [ Setting . THEME _DARK ] = _ ( 'Dark' ) ;
2020-02-22 11:03:49 +02:00
if ( platform !== mobilePlatform ) {
2019-10-02 08:49:38 +02:00
output [ Setting . THEME _DRACULA ] = _ ( 'Dracula' ) ;
2019-07-30 11:37:52 +02:00
output [ Setting . THEME _SOLARIZED _LIGHT ] = _ ( 'Solarised Light' ) ;
output [ Setting . THEME _SOLARIZED _DARK ] = _ ( 'Solarised Dark' ) ;
2019-10-30 11:55:45 +02:00
output [ Setting . THEME _NORD ] = _ ( 'Nord' ) ;
2020-04-07 11:23:40 +02:00
output [ Setting . THEME _ARITIM _DARK ] = _ ( 'Aritim Dark' ) ;
2020-02-12 01:32:15 +02:00
} else {
output [ Setting . THEME _OLED _DARK ] = _ ( 'OLED Dark' ) ;
2019-07-30 11:37:52 +02:00
}
2019-07-29 15:43:53 +02:00
return output ;
} ,
} ,
2019-11-11 10:44:54 +02:00
showNoteCounts : { value : true , type : Setting . TYPE _BOOL , public : true , appTypes : [ 'desktop' ] , label : ( ) => _ ( 'Show note counts' ) } ,
2019-10-31 10:47:45 +02:00
layoutButtonSequence : {
2019-10-31 10:42:17 +02:00
value : Setting . LAYOUT _ALL ,
type : Setting . TYPE _INT ,
public : false ,
appTypes : [ 'desktop' ] ,
isEnum : true ,
options : ( ) => ( {
[ Setting . LAYOUT _ALL ] : _ ( '%s / %s / %s' , _ ( 'Editor' ) , _ ( 'Viewer' ) , _ ( 'Split View' ) ) ,
[ Setting . LAYOUT _EDITOR _VIEWER ] : _ ( '%s / %s' , _ ( 'Editor' ) , _ ( 'Viewer' ) ) ,
[ Setting . LAYOUT _EDITOR _SPLIT ] : _ ( '%s / %s' , _ ( 'Editor' ) , _ ( 'Split View' ) ) ,
[ Setting . LAYOUT _VIEWER _SPLIT ] : _ ( '%s / %s' , _ ( 'Viewer' ) , _ ( 'Split View' ) ) ,
2020-03-10 01:24:57 +02:00
[ Setting . LAYOUT _SPLIT _WYSIWYG ] : _ ( '%s / %s' , _ ( 'Split' ) , 'WYSIWYG (Experimental)' ) ,
2019-10-31 10:42:17 +02:00
} ) ,
} ,
2019-07-29 15:43:53 +02:00
uncompletedTodosOnTop : { value : true , type : Setting . TYPE _BOOL , section : 'note' , public : true , appTypes : [ 'cli' ] , label : ( ) => _ ( 'Uncompleted to-dos on top' ) } ,
showCompletedTodos : { value : true , type : Setting . TYPE _BOOL , section : 'note' , public : true , appTypes : [ 'cli' ] , label : ( ) => _ ( 'Show completed to-dos' ) } ,
'notes.sortOrder.field' : {
value : 'user_updated_time' ,
type : Setting . TYPE _STRING ,
section : 'note' ,
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 ;
} ,
} ,
2020-01-07 00:27:37 +02:00
'editor.autoMatchingBraces' : {
value : true ,
type : Setting . TYPE _BOOL ,
public : true ,
section : 'note' ,
appTypes : [ 'desktop' ] ,
label : ( ) => _ ( 'Auto-pair braces, parenthesis, quotations, etc.' ) ,
} ,
2019-01-28 01:15:56 +02:00
'notes.sortOrder.reverse' : { value : true , type : Setting . TYPE _BOOL , section : 'note' , public : true , label : ( ) => _ ( 'Reverse sort order' ) , appTypes : [ 'cli' ] } ,
2019-07-29 15:43:53 +02:00
'folders.sortOrder.field' : {
value : 'title' ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
appTypes : [ 'cli' ] ,
label : ( ) => _ ( 'Sort notebooks by' ) ,
options : ( ) => {
const Folder = require ( 'lib/models/Folder' ) ;
const folderSortFields = [ 'title' , 'last_note_user_updated_time' ] ;
const options = { } ;
for ( let i = 0 ; i < folderSortFields . length ; i ++ ) {
options [ folderSortFields [ i ] ] = toTitleCase ( Folder . fieldToLabel ( folderSortFields [ i ] ) ) ;
}
return options ;
} ,
} ,
2019-05-22 17:18:16 +02:00
'folders.sortOrder.reverse' : { value : false , type : Setting . TYPE _BOOL , public : true , label : ( ) => _ ( 'Reverse sort order' ) , appTypes : [ 'cli' ] } ,
2019-07-29 15:43:53 +02:00
trackLocation : { value : true , type : Setting . TYPE _BOOL , section : 'note' , public : true , label : ( ) => _ ( 'Save geo-location with notes' ) } ,
2020-03-25 12:50:45 +02:00
'editor.beta' : {
value : false ,
type : Setting . TYPE _BOOL ,
section : 'note' ,
public : true ,
appTypes : [ 'mobile' ] ,
label : ( ) => 'Opt-in to the editor beta' ,
description : ( ) => 'This beta adds list continuation, Markdown preview, and Markdown shortcuts. If you find bugs, please report them in the Discourse forum.' ,
} ,
2019-07-29 15:43:53 +02:00
newTodoFocus : {
value : 'title' ,
type : Setting . TYPE _STRING ,
section : 'note' ,
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 ,
section : 'note' ,
isEnum : true ,
public : true ,
appTypes : [ 'desktop' ] ,
label : ( ) => _ ( 'When creating a new note:' ) ,
options : ( ) => {
return {
title : _ ( 'Focus title' ) ,
body : _ ( 'Focus body' ) ,
} ;
} ,
} ,
2019-12-29 19:58:40 +02:00
// Deprecated - use markdown.plugin.*
2020-02-05 00:09:34 +02:00
'markdown.softbreaks' : { value : false , type : Setting . TYPE _BOOL , public : false , appTypes : [ 'mobile' , 'desktop' ] } ,
'markdown.typographer' : { value : false , type : Setting . TYPE _BOOL , public : false , appTypes : [ 'mobile' , 'desktop' ] } ,
2019-12-29 19:58:40 +02:00
// Deprecated
2020-04-10 20:16:18 +02:00
'markdown.plugin.softbreaks' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable soft breaks' ) } ${ wysiwygYes } ` } ,
'markdown.plugin.typographer' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable typographer support' ) } ${ wysiwygYes } ` } ,
'markdown.plugin.katex' : { value : true , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable math expressions' ) } ${ wysiwygYes } ` } ,
'markdown.plugin.fountain' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable Fountain syntax support' ) } ${ wysiwygYes } ` } ,
'markdown.plugin.mermaid' : { value : true , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable Mermaid diagrams support' ) } ${ wysiwygYes } ` } ,
2020-04-10 19:33:09 +02:00
2020-04-10 20:16:18 +02:00
'markdown.plugin.mark' : { value : true , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable ==mark== syntax' ) } ${ wysiwygNo } ` } ,
'markdown.plugin.footnote' : { value : true , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable footnotes' ) } ${ wysiwygNo } ` } ,
'markdown.plugin.toc' : { value : true , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable table of contents extension' ) } ${ wysiwygNo } ` } ,
'markdown.plugin.sub' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable ~sub~ syntax' ) } ${ wysiwygNo } ` } ,
'markdown.plugin.sup' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable ^sup^ syntax' ) } ${ wysiwygNo } ` } ,
'markdown.plugin.deflist' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable deflist syntax' ) } ${ wysiwygNo } ` } ,
'markdown.plugin.abbr' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable abbreviation syntax' ) } ${ wysiwygNo } ` } ,
'markdown.plugin.emoji' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable markdown emoji' ) } ${ wysiwygNo } ` } ,
'markdown.plugin.insert' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable ++insert++ syntax' ) } ${ wysiwygNo } ` } ,
'markdown.plugin.multitable' : { value : false , type : Setting . TYPE _BOOL , section : 'plugins' , public : true , appTypes : [ 'mobile' , 'desktop' ] , label : ( ) => ` ${ _ ( 'Enable multimarkdown table extension' ) } ${ wysiwygNo } ` } ,
2018-04-16 19:18:28 +02:00
// Tray icon (called AppIndicator) doesn't work in Ubuntu
// http://www.webupd8.org/2017/04/fix-appindicator-not-working-for.html
// Might be fixed in Electron 18.x but no non-beta release yet. So for now
// by default we disable it on Linux.
2019-07-29 15:43:53 +02:00
showTrayIcon : {
value : platform !== 'linux' ,
type : Setting . TYPE _BOOL ,
section : 'application' ,
public : true ,
appTypes : [ 'desktop' ] ,
label : ( ) => _ ( 'Show tray icon' ) ,
description : ( ) => {
return platform === 'linux' ? _ ( 'Note: Does not work in all desktop environments.' ) : _ ( 'This will allow Joplin to run in the background. It is recommended to enable this setting so that your notes are constantly being synchronised, thus reducing the number of conflicts.' ) ;
} ,
} ,
startMinimized : { value : false , type : Setting . TYPE _BOOL , section : 'application' , public : true , appTypes : [ 'desktop' ] , label : ( ) => _ ( 'Start application minimised in the tray icon' ) } ,
collapsedFolderIds : { value : [ ] , type : Setting . TYPE _ARRAY , public : false } ,
2019-01-09 19:25:44 +02:00
2018-12-28 22:40:29 +02:00
'db.ftsEnabled' : { value : - 1 , type : Setting . TYPE _INT , public : false } ,
2017-12-12 23:58:57 +02:00
'encryption.enabled' : { value : false , type : Setting . TYPE _BOOL , public : false } ,
2017-12-14 02:23:32 +02:00
'encryption.activeMasterKeyId' : { value : '' , type : Setting . TYPE _STRING , public : false } ,
2018-11-02 21:22:49 +02:00
'encryption.passwordCache' : { value : { } , type : Setting . TYPE _OBJECT , public : false , secure : true } ,
2020-03-13 19:42:50 +02:00
'encryption.shouldReencrypt' : {
value : - 1 , // will be set on app startup
type : Setting . TYPE _INT ,
public : false ,
} ,
2020-02-12 14:41:32 +02:00
// Deprecated in favour of windowContentZoomFactor
'style.zoom' : { value : 100 , type : Setting . TYPE _INT , public : false , appTypes : [ 'desktop' ] , section : 'appearance' , label : ( ) => '' , minimum : 50 , maximum : 500 , step : 10 } ,
2019-07-29 15:43:53 +02:00
'style.editor.fontSize' : { value : 13 , type : Setting . TYPE _INT , public : true , appTypes : [ 'desktop' ] , section : 'appearance' , label : ( ) => _ ( 'Editor font size' ) , minimum : 4 , maximum : 50 , step : 1 } ,
2019-09-17 22:32:00 +02:00
'style.editor.fontFamily' :
2019-09-19 23:51:18 +02:00
( mobilePlatform ) ?
2019-09-17 22:32:00 +02:00
( {
value : Setting . FONT _DEFAULT ,
type : Setting . TYPE _STRING ,
isEnum : true ,
public : true ,
label : ( ) => _ ( 'Editor font' ) ,
appTypes : [ 'mobile' ] ,
section : 'appearance' ,
options : ( ) => {
// IMPORTANT: The font mapping must match the one in global-styles.js::editorFont()
if ( mobilePlatform === 'ios' ) {
return {
[ Setting . FONT _DEFAULT ] : 'Default' ,
[ Setting . FONT _MENLO ] : 'Menlo' ,
[ Setting . FONT _COURIER _NEW ] : 'Courier New' ,
[ Setting . FONT _AVENIR ] : 'Avenir' ,
} ;
}
return {
[ Setting . FONT _DEFAULT ] : 'Default' ,
[ Setting . FONT _MONOSPACE ] : 'Monospace' ,
} ;
} ,
} ) : {
value : '' ,
type : Setting . TYPE _STRING ,
public : true ,
appTypes : [ 'desktop' ] ,
section : 'appearance' ,
label : ( ) => _ ( 'Editor font family' ) ,
description : ( ) =>
_ ( 'This must be *monospace* font or it will not work properly. If the font ' +
'is incorrect or empty, it will default to a generic monospace font.' ) ,
} ,
2019-07-29 15:43:53 +02:00
'style.sidebar.width' : { value : 150 , minimum : 80 , maximum : 400 , type : Setting . TYPE _INT , public : false , appTypes : [ 'desktop' ] } ,
'style.noteList.width' : { value : 150 , minimum : 80 , maximum : 400 , type : Setting . TYPE _INT , public : false , appTypes : [ 'desktop' ] } ,
2019-12-13 02:40:58 +02:00
// TODO: Is there a better way to do this? The goal here is to simply have
// a way to display a link to the customizable stylesheets, not for it to
// serve as a customizable Setting. But because the Setting page is auto-
// generated from this list of settings, there wasn't a really elegant way
// to do that directly in the React markup.
'style.customCss.renderedMarkdown' : {
onClick : ( ) => {
const dir = Setting . value ( 'profileDir' ) ;
const filename = Setting . custom _css _files . RENDERED _MARKDOWN ;
const filepath = ` ${ dir } / ${ filename } ` ;
const defaultContents = '/* For styling the rendered Markdown */' ;
shim . openOrCreateFile ( filepath , defaultContents ) ;
} ,
type : Setting . TYPE _BUTTON ,
public : true ,
appTypes : [ 'desktop' ] ,
label : ( ) => _ ( 'Custom stylesheet for rendered Markdown' ) ,
section : 'appearance' ,
2020-03-06 20:22:40 +02:00
advanced : true ,
2019-12-13 02:40:58 +02:00
} ,
'style.customCss.joplinApp' : {
onClick : ( ) => {
const dir = Setting . value ( 'profileDir' ) ;
const filename = Setting . custom _css _files . JOPLIN _APP ;
const filepath = ` ${ dir } / ${ filename } ` ;
const defaultContents = ` /* For styling the entire Joplin app (except the rendered Markdown, which is defined in \` ${ Setting . custom _css _files . RENDERED _MARKDOWN } \` ) */ ` ;
shim . openOrCreateFile ( filepath , defaultContents ) ;
} ,
type : Setting . TYPE _BUTTON ,
public : true ,
appTypes : [ 'desktop' ] ,
label : ( ) => _ ( 'Custom stylesheet for Joplin-wide app styles' ) ,
section : 'appearance' ,
2020-03-06 20:22:40 +02:00
advanced : true ,
description : ( ) => 'CSS file support is provided for your convenience, but they are advanced settings, and styles you define may break from one version to the next. If you want to use them, please know that it might require regular development work from you to keep them working. The Joplin team cannot make a commitment to keep the application HTML structure stable.' ,
2019-12-13 02:40:58 +02:00
} ,
2019-07-29 15:43:53 +02:00
autoUpdateEnabled : { value : true , type : Setting . TYPE _BOOL , section : 'application' , public : true , appTypes : [ 'desktop' ] , label : ( ) => _ ( 'Automatically update the application' ) } ,
'autoUpdate.includePreReleases' : { value : false , type : Setting . TYPE _BOOL , section : 'application' , public : true , appTypes : [ 'desktop' ] , label : ( ) => _ ( 'Get pre-releases when checking for updates' ) , description : ( ) => _ ( 'See the pre-release page for more details: %s' , 'https://joplinapp.org/prereleases' ) } ,
2018-05-25 14:30:27 +02:00
'clipperServer.autoStart' : { value : false , type : Setting . TYPE _BOOL , public : false } ,
2019-07-29 15:43:53 +02:00
'sync.interval' : {
value : 300 ,
type : Setting . TYPE _INT ,
section : 'sync' ,
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' ] } ,
sidebarVisibility : { value : true , type : Setting . TYPE _BOOL , public : false , appTypes : [ 'desktop' ] } ,
2019-10-30 11:40:34 +02:00
noteListVisibility : { value : true , type : Setting . TYPE _BOOL , public : false , appTypes : [ 'desktop' ] } ,
2019-07-29 15:43:53 +02:00
tagHeaderIsExpanded : { value : true , type : Setting . TYPE _BOOL , public : false , appTypes : [ 'desktop' ] } ,
folderHeaderIsExpanded : { value : true , type : Setting . TYPE _BOOL , public : false , appTypes : [ 'desktop' ] } ,
editor : { value : '' , type : Setting . TYPE _STRING , subType : 'file_path_and_args' , public : true , appTypes : [ 'cli' , 'desktop' ] , label : ( ) => _ ( 'Text editor command' ) , description : ( ) => _ ( 'The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.' ) } ,
2019-10-12 01:21:19 +02:00
'export.pdfPageSize' : { value : 'A4' , type : Setting . TYPE _STRING , isEnum : true , public : true , appTypes : [ 'desktop' ] , label : ( ) => _ ( 'Page size for PDF export' ) , options : ( ) => {
return {
'A4' : _ ( 'A4' ) ,
'Letter' : _ ( 'Letter' ) ,
'A3' : _ ( 'A3' ) ,
'A5' : _ ( 'A5' ) ,
'Tabloid' : _ ( 'Tabloid' ) ,
'Legal' : _ ( 'Legal' ) ,
} ;
2020-02-05 00:09:34 +02:00
} } ,
2019-10-12 01:21:19 +02:00
'export.pdfPageOrientation' : { value : 'portrait' , type : Setting . TYPE _STRING , isEnum : true , public : true , appTypes : [ 'desktop' ] , label : ( ) => _ ( 'Page orientation for PDF export' ) , options : ( ) => {
return {
'portrait' : _ ( 'Portrait' ) ,
'landscape' : _ ( 'Landscape' ) ,
} ;
2020-02-05 00:09:34 +02:00
} } ,
2019-10-12 01:21:19 +02:00
2019-07-29 15:43:53 +02:00
'net.customCertificates' : {
value : '' ,
type : Setting . TYPE _STRING ,
section : 'sync' ,
2019-12-13 03:16:34 +02:00
advanced : true ,
2019-07-29 15:43:53 +02:00
show : settings => {
return [ SyncTargetRegistry . nameToId ( 'nextcloud' ) , SyncTargetRegistry . nameToId ( 'webdav' ) ] . indexOf ( settings [ 'sync.target' ] ) >= 0 ;
} ,
public : true ,
appTypes : [ 'desktop' , 'cli' ] ,
label : ( ) => _ ( 'Custom TLS certificates' ) ,
description : ( ) => _ ( 'Comma-separated list of paths to directories to load the certificates from, or path to individual cert files. For example: /my/cert_dir, /other/custom.pem. Note that if you make changes to the TLS settings, you must save your changes before clicking on "Check synchronisation configuration".' ) ,
} ,
'net.ignoreTlsErrors' : {
value : false ,
type : Setting . TYPE _BOOL ,
2019-12-13 03:16:34 +02:00
advanced : true ,
2019-07-29 15:43:53 +02:00
section : 'sync' ,
show : settings => {
return [ SyncTargetRegistry . nameToId ( 'nextcloud' ) , SyncTargetRegistry . nameToId ( 'webdav' ) ] . indexOf ( settings [ 'sync.target' ] ) >= 0 ;
} ,
public : true ,
appTypes : [ 'desktop' , 'cli' ] ,
label : ( ) => _ ( 'Ignore TLS certificate errors' ) ,
} ,
2019-01-09 19:25:44 +02:00
2020-03-25 12:50:45 +02:00
'sync.wipeOutFailSafe' : {
value : true ,
type : Setting . TYPE _BOOL ,
advanced : true ,
public : true ,
section : 'sync' ,
label : ( ) => _ ( 'Fail-safe' ) ,
description : ( ) => _ ( 'Fail-safe: Do not wipe out local data when sync target is empty (often the result of a misconfiguration or bug)' ) ,
} ,
2019-09-25 20:40:04 +02:00
2018-09-28 20:24:57 +02:00
'api.token' : { value : null , type : Setting . TYPE _STRING , public : false } ,
2019-09-08 18:16:45 +02:00
'api.port' : { value : null , type : Setting . TYPE _INT , public : true , appTypes : [ 'cli' ] , description : ( ) => _ ( 'Specify the port that should be used by the API server. If not set, a default will be used.' ) } ,
2018-12-10 02:39:31 +02:00
'resourceService.lastProcessedChangeId' : { value : 0 , type : Setting . TYPE _INT , public : false } ,
2018-12-10 20:54:46 +02:00
'searchEngine.lastProcessedChangeId' : { value : 0 , type : Setting . TYPE _INT , public : false } ,
2019-05-06 22:35:29 +02:00
'revisionService.lastProcessedChangeId' : { value : 0 , type : Setting . TYPE _INT , public : false } ,
2019-07-29 15:43:53 +02:00
2019-01-13 18:05:07 +02:00
'searchEngine.initialIndexingDone' : { value : false , type : Setting . TYPE _BOOL , public : false } ,
2019-05-06 22:35:29 +02:00
'revisionService.enabled' : { section : 'revisionService' , value : true , type : Setting . TYPE _BOOL , public : true , label : ( ) => _ ( 'Enable note history' ) } ,
2019-07-29 15:43:53 +02:00
'revisionService.ttlDays' : {
section : 'revisionService' ,
value : 90 ,
type : Setting . TYPE _INT ,
public : true ,
minimum : 1 ,
maximum : 365 * 2 ,
step : 1 ,
unitLabel : ( value = null ) => {
return value === null ? _ ( 'days' ) : _ ( '%d days' , value ) ;
} ,
label : ( ) => _ ( 'Keep note history for' ) ,
} ,
2019-05-08 01:10:36 +02:00
'revisionService.intervalBetweenRevisions' : { section : 'revisionService' , value : 1000 * 60 * 10 , type : Setting . TYPE _INT , public : false } ,
2019-05-08 01:51:56 +02:00
'revisionService.oldNoteInterval' : { section : 'revisionService' , value : 1000 * 60 * 60 * 24 * 7 , type : Setting . TYPE _INT , public : false } ,
2019-05-06 22:35:29 +02:00
2019-02-08 01:27:50 +02:00
'welcome.wasBuilt' : { value : false , type : Setting . TYPE _BOOL , public : false } ,
2019-05-12 02:10:46 +02:00
'welcome.enabled' : { value : true , type : Setting . TYPE _BOOL , public : false } ,
2019-10-16 23:52:22 +02:00
'camera.type' : { value : 0 , type : Setting . TYPE _INT , public : false , appTypes : [ 'mobile' ] } ,
'camera.ratio' : { value : '4:3' , type : Setting . TYPE _STRING , public : false , appTypes : [ 'mobile' ] } ,
2020-02-12 14:41:32 +02:00
windowContentZoomFactor : {
value : 100 ,
type : Setting . TYPE _INT ,
public : false ,
appTypes : [ 'desktop' ] ,
minimum : 30 ,
maximum : 300 ,
step : 10 ,
} ,
2017-11-24 21:21:30 +02:00
} ;
return this . metadata _ ;
}
2017-07-24 20:58:11 +02:00
static settingMetadata ( key ) {
2017-11-24 21:21:30 +02:00
const metadata = this . metadata ( ) ;
2019-09-19 23:51:18 +02:00
if ( ! ( key in metadata ) ) throw new Error ( ` Unknown key: ${ key } ` ) ;
2020-03-14 01:46:14 +02:00
const output = Object . assign ( { } , metadata [ key ] ) ;
2017-05-12 22:17:23 +02:00
output . key = key ;
return output ;
}
2017-10-30 02:29:10 +02:00
static keyExists ( key ) {
2017-11-24 21:21:30 +02:00
return key in this . metadata ( ) ;
2017-10-30 02:29:10 +02:00
}
2018-03-02 20:16:48 +02: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 21:21:30 +02:00
const metadata = this . metadata ( ) ;
2017-08-22 19:57:35 +02:00
this . keys _ = [ ] ;
2020-03-14 01:46:14 +02:00
for ( const n in metadata ) {
2017-11-24 21:21:30 +02:00
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 ) {
2020-03-14 01:46:14 +02:00
const output = [ ] ;
2017-08-22 19:57:35 +02:00
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 22:16:03 +02: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 22:17:23 +02:00
static load ( ) {
2017-07-26 19:49:01 +02:00
this . cancelScheduleSave ( ) ;
2017-05-12 22:17:23 +02:00
this . cache _ = [ ] ;
2019-07-29 15:43:53 +02:00
return this . modelSelectAll ( 'SELECT * FROM settings' ) . then ( rows => {
2017-07-26 20:36:16 +02:00
this . cache _ = [ ] ;
2017-07-24 22:36:49 +02:00
2017-07-26 20:36:16 +02:00
for ( let i = 0 ; i < rows . length ; i ++ ) {
2020-03-14 01:46:14 +02:00
const c = rows [ i ] ;
2017-07-26 20:36:16 +02:00
2017-10-30 02:29:10 +02:00
if ( ! this . keyExists ( c . key ) ) continue ;
2017-07-25 23:55:26 +02:00
c . value = this . formatValue ( c . key , c . value ) ;
2018-05-08 12:29:25 +02:00
c . value = this . filterValue ( c . key , c . value ) ;
2017-07-25 23:55:26 +02:00
2017-07-26 20:36:16 +02:00
this . cache _ . push ( c ) ;
2017-07-24 22:36:49 +02:00
}
2017-07-26 19:49:01 +02:00
2017-10-21 18:53:43 +02:00
this . dispatchUpdateAll ( ) ;
} ) ;
}
2017-11-24 01:10:55 +02:00
static toPlainObject ( ) {
2017-10-21 18:53:43 +02:00
const keys = this . keys ( ) ;
2020-03-14 01:46:14 +02:00
const keyToValues = { } ;
2017-10-21 18:53:43 +02:00
for ( let i = 0 ; i < keys . length ; i ++ ) {
keyToValues [ keys [ i ] ] = this . value ( keys [ i ] ) ;
}
2017-11-24 01:10:55 +02:00
return keyToValues ;
}
2017-07-26 19:49:01 +02:00
2017-11-24 01:10:55 +02:00
static dispatchUpdateAll ( ) {
2017-10-21 18:53:43 +02:00
this . dispatch ( {
2017-11-08 23:22:24 +02:00
type : 'SETTING_UPDATE_ALL' ,
2017-11-24 01:10:55 +02:00
settings : this . toPlainObject ( ) ,
2017-05-12 22:17:23 +02:00
} ) ;
}
2017-06-24 20:51:43 +02:00
static setConstant ( key , value ) {
2019-09-19 23:51:18 +02:00
if ( ! ( key in this . constants _ ) ) throw new Error ( ` Unknown constant key: ${ key } ` ) ;
2017-06-24 20:51:43 +02:00
this . constants _ [ key ] = value ;
}
2017-05-12 22:17:23 +02:00
static setValue ( key , value ) {
2017-06-19 20:58:49 +02: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-05-08 12:29:25 +02:00
value = this . filterValue ( key , value ) ;
2018-04-15 17:50:39 +02:00
2017-05-12 22:17:23 +02:00
for ( let i = 0 ; i < this . cache _ . length ; i ++ ) {
2020-03-14 01:46:14 +02:00
const c = this . cache _ [ i ] ;
2017-07-24 20:58:11 +02:00
if ( c . key == key ) {
const md = this . settingMetadata ( key ) ;
2017-07-25 23:55:26 +02:00
if ( md . isEnum === true ) {
2017-07-24 20:58:11 +02: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 23:55:26 +02:00
2018-01-17 23:17:40 +02: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 23:55:26 +02:00
2019-07-29 15:43:53 +02:00
if ( 'minimum' in md && value < md . minimum ) value = md . minimum ;
if ( 'maximum' in md && value > md . maximum ) value = md . maximum ;
2019-02-16 03:12:43 +02:00
2017-11-14 20:02:58 +02:00
c . value = value ;
2017-07-26 19:49:01 +02:00
this . dispatch ( {
2017-11-08 23:22:24 +02:00
type : 'SETTING_UPDATE_ONE' ,
2017-07-26 19:49:01 +02:00
key : key ,
value : c . value ,
} ) ;
this . scheduleSave ( ) ;
2017-05-12 22:17:23 +02:00
return ;
}
}
2017-07-25 23:55:26 +02:00
this . cache _ . push ( {
key : key ,
value : this . formatValue ( key , value ) ,
} ) ;
2017-07-26 19:49:01 +02:00
this . dispatch ( {
2017-11-08 23:22:24 +02:00
type : 'SETTING_UPDATE_ONE' ,
2017-07-26 19:49:01 +02:00
key : key ,
value : this . formatValue ( key , value ) ,
} ) ;
this . scheduleSave ( ) ;
}
2020-02-12 14:41:32 +02:00
static incValue ( key , inc ) {
return this . setValue ( key , this . value ( key ) + inc ) ;
}
2017-12-17 21:51:45 +02:00
static setObjectKey ( settingKey , objectKey , value ) {
2017-12-28 21:57:21 +02:00
let o = this . value ( settingKey ) ;
2017-12-17 21:51:45 +02:00
if ( typeof o !== 'object' ) o = { } ;
o [ objectKey ] = value ;
this . setValue ( settingKey , o ) ;
}
static deleteObjectKey ( settingKey , objectKey ) {
const o = this . value ( settingKey ) ;
if ( typeof o !== 'object' ) return ;
delete o [ objectKey ] ;
this . setValue ( settingKey , o ) ;
}
2017-07-26 19:49:01 +02: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 ) ;
if ( md . type == Setting . TYPE _BOOL ) return value ? '1' : '0' ;
2017-11-12 19:02:20 +02:00
if ( md . type == Setting . TYPE _ARRAY ) return value ? JSON . stringify ( value ) : '[]' ;
2017-11-14 20:02:58 +02:00
if ( md . type == Setting . TYPE _OBJECT ) return value ? JSON . stringify ( value ) : '{}' ;
2019-09-19 23:51:18 +02:00
if ( md . type == Setting . TYPE _STRING ) return value ? ` ${ value } ` : '' ;
2018-01-07 21:20:10 +02:00
2019-09-19 23:51:18 +02:00
throw new Error ( ` Unhandled value type: ${ md . type } ` ) ;
2017-05-12 22:17:23 +02:00
}
2018-05-08 12:29:25 +02:00
static filterValue ( key , value ) {
const md = this . settingMetadata ( key ) ;
return md . filter ? md . filter ( value ) : value ;
}
2017-07-25 23:55:26 +02:00
static formatValue ( key , value ) {
const md = this . settingMetadata ( key ) ;
2017-11-12 19:02:20 +02:00
2018-01-07 21:29:57 +02:00
if ( md . type == Setting . TYPE _INT ) return ! value ? 0 : Math . floor ( Number ( value ) ) ;
2017-11-12 19:02:20 +02:00
2017-07-26 19:49:01 +02:00
if ( md . type == Setting . TYPE _BOOL ) {
2017-07-26 23:07:27 +02:00
if ( typeof value === 'string' ) {
value = value . toLowerCase ( ) ;
if ( value === 'true' ) return true ;
if ( value === 'false' ) return false ;
value = Number ( value ) ;
}
2017-07-26 19:49:01 +02:00
return ! ! value ;
}
2017-11-12 19:02:20 +02:00
if ( md . type === Setting . TYPE _ARRAY ) {
2017-11-14 20:02:58 +02:00
if ( ! value ) return [ ] ;
2017-11-12 19:02:20 +02:00
if ( Array . isArray ( value ) ) return value ;
if ( typeof value === 'string' ) return JSON . parse ( value ) ;
return [ ] ;
}
2017-11-14 20:02:58 +02:00
if ( md . type === Setting . TYPE _OBJECT ) {
if ( ! value ) return { } ;
if ( typeof value === 'object' ) return value ;
if ( typeof value === 'string' ) return JSON . parse ( value ) ;
return { } ;
}
2018-01-07 21:20:10 +02:00
if ( md . type === Setting . TYPE _STRING ) {
if ( ! value ) return '' ;
2019-09-19 23:51:18 +02:00
return ` ${ value } ` ;
2018-01-07 21:20:10 +02:00
}
2019-09-19 23:51:18 +02:00
throw new Error ( ` Unhandled value type: ${ md . type } ` ) ;
2017-07-25 20:57:06 +02:00
}
2017-05-12 22:17:23 +02:00
static value ( key ) {
2017-12-14 21:39:13 +02: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 21:20:10 +02:00
if ( value === null || value === undefined ) return value ;
2017-12-14 21:39:13 +02:00
if ( Array . isArray ( value ) ) return value . slice ( ) ;
if ( typeof value === 'object' ) return Object . assign ( { } , value ) ;
return value ;
}
2017-07-07 00:15:31 +02:00
if ( key in this . constants _ ) {
2017-11-20 00:08:58 +02:00
const v = this . constants _ [ key ] ;
const output = typeof v === 'function' ? v ( ) : v ;
2019-09-19 23:51:18 +02:00
if ( output == 'SET_ME' ) throw new Error ( ` Setting constant has not been set: ${ key } ` ) ;
2017-07-07 00:15:31 +02:00
return output ;
}
2017-06-24 20:51:43 +02:00
2017-06-19 20:58:49 +02:00
if ( ! this . cache _ ) throw new Error ( 'Settings have not been initialized!' ) ;
2017-05-12 22:17:23 +02:00
for ( let i = 0 ; i < this . cache _ . length ; i ++ ) {
if ( this . cache _ [ i ] . key == key ) {
2017-12-14 21:39:13 +02:00
return copyIfNeeded ( this . cache _ [ i ] . value ) ;
2017-05-12 22:17:23 +02:00
}
}
2017-07-25 23:55:26 +02:00
const md = this . settingMetadata ( key ) ;
2017-12-14 21:39:13 +02:00
return copyIfNeeded ( md . value ) ;
2017-05-12 22:17:23 +02:00
}
2017-07-24 20:58:11 +02:00
static isEnum ( key ) {
const md = this . settingMetadata ( key ) ;
2017-07-25 23:55:26 +02:00
return md . isEnum === true ;
2017-07-24 20:58:11 +02:00
}
static enumOptionValues ( key ) {
const options = this . enumOptions ( key ) ;
2020-03-14 01:46:14 +02:00
const output = [ ] ;
for ( const n in options ) {
2017-07-24 20:58:11 +02:00
if ( ! options . hasOwnProperty ( n ) ) continue ;
output . push ( n ) ;
}
return output ;
}
static enumOptionLabel ( key , value ) {
const options = this . enumOptions ( key ) ;
2020-03-14 01:46:14 +02:00
for ( const n in options ) {
2017-07-24 20:58:11 +02:00
if ( n == value ) return options [ n ] ;
}
return '' ;
}
static enumOptions ( key ) {
2017-11-24 21:21:30 +02:00
const metadata = this . metadata ( ) ;
2019-09-19 23:51:18 +02:00
if ( ! metadata [ key ] ) throw new Error ( ` Unknown key: ${ key } ` ) ;
if ( ! metadata [ key ] . options ) throw new Error ( ` No options for: ${ key } ` ) ;
2017-11-24 21:21:30 +02:00
return metadata [ key ] . options ( ) ;
2017-07-24 20:58:11 +02:00
}
2017-10-20 00:02:13 +02:00
static enumOptionsDoc ( key , templateString = null ) {
if ( templateString === null ) templateString = '%s: %s' ;
2017-07-24 20:58:11 +02:00
const options = this . enumOptions ( key ) ;
2020-03-14 01:46:14 +02:00
const output = [ ] ;
for ( const n in options ) {
2017-07-24 20:58:11 +02:00
if ( ! options . hasOwnProperty ( n ) ) continue ;
2017-10-20 00:02:13 +02:00
output . push ( sprintf ( templateString , n , options [ n ] ) ) ;
2017-07-24 20:58:11 +02:00
}
return output . join ( ', ' ) ;
}
static isAllowedEnumOption ( key , value ) {
const options = this . enumOptions ( key ) ;
return ! ! options [ value ] ;
}
2018-02-06 20:59:36 +02: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' }
2019-12-29 19:58:40 +02:00
static subValues ( baseKey , settings , options = null ) {
const includeBaseKeyInName = ! ! options && ! ! options . includeBaseKeyInName ;
2020-03-14 01:46:14 +02:00
const output = { } ;
for ( const key in settings ) {
2018-02-06 20:59:36 +02:00
if ( ! settings . hasOwnProperty ( key ) ) continue ;
if ( key . indexOf ( baseKey ) === 0 ) {
2019-12-29 19:58:40 +02:00
const subKey = includeBaseKeyInName ? key : key . substr ( baseKey . length + 1 ) ;
2018-02-06 20:59:36 +02:00
output [ subKey ] = settings [ key ] ;
2017-05-16 23:46:21 +02:00
}
}
return output ;
}
2018-02-15 19:12:09 +02:00
static async saveAll ( ) {
2017-07-26 19:49:01 +02:00
if ( ! this . saveTimeoutId _ ) return Promise . resolve ( ) ;
2017-05-19 21:12:09 +02:00
2017-06-25 12:41:03 +02:00
this . logger ( ) . info ( 'Saving settings...' ) ;
2017-07-26 19:49:01 +02:00
clearTimeout ( this . saveTimeoutId _ ) ;
this . saveTimeoutId _ = null ;
2017-05-19 21:12:09 +02:00
2020-03-14 01:46:14 +02:00
const queries = [ ] ;
2017-06-11 23:11:14 +02:00
queries . push ( 'DELETE FROM settings' ) ;
for ( let i = 0 ; i < this . cache _ . length ; i ++ ) {
2020-03-14 01:46:14 +02:00
const s = Object . assign ( { } , this . cache _ [ i ] ) ;
2017-07-26 19:49:01 +02:00
s . value = this . valueToString ( s . key , s . value ) ;
queries . push ( Database . insertQuery ( this . tableName ( ) , s ) ) ;
2017-06-11 23:11:14 +02:00
}
2018-02-15 19:12:09 +02:00
await BaseModel . db ( ) . transactionExecBatch ( queries ) ;
2018-04-15 17:50:39 +02:00
2018-02-15 19:12:09 +02:00
this . logger ( ) . info ( 'Settings have been saved.' ) ;
2017-05-19 21:12:09 +02:00
}
2017-07-26 19:49:01 +02:00
static scheduleSave ( ) {
2018-02-15 19:12:09 +02:00
if ( ! Setting . autoSaveEnabled ) return ;
2017-07-26 19:49:01 +02:00
if ( this . saveTimeoutId _ ) clearTimeout ( this . saveTimeoutId _ ) ;
2017-05-19 21:12:09 +02:00
2017-07-26 19:49:01 +02:00
this . saveTimeoutId _ = setTimeout ( ( ) => {
2017-05-19 21:12:09 +02:00
this . saveAll ( ) ;
2017-05-12 22:17:23 +02:00
} , 500 ) ;
}
2017-07-26 19:49:01 +02:00
static cancelScheduleSave ( ) {
if ( this . saveTimeoutId _ ) clearTimeout ( this . saveTimeoutId _ ) ;
this . saveTimeoutId _ = null ;
2017-06-19 21:26:27 +02:00
}
2017-07-23 20:26:50 +02:00
static publicSettings ( appType ) {
if ( ! appType ) throw new Error ( 'appType is required' ) ;
2017-11-24 21:21:30 +02:00
const metadata = this . metadata ( ) ;
2020-03-14 01:46:14 +02:00
const output = { } ;
for ( const key in metadata ) {
2017-11-24 21:21:30 +02:00
if ( ! metadata . hasOwnProperty ( key ) ) continue ;
2020-03-14 01:46:14 +02:00
const s = Object . assign ( { } , metadata [ key ] ) ;
2017-07-23 20:26:50 +02: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-20 00:02:13 +02:00
static typeToString ( typeId ) {
if ( typeId === Setting . TYPE _INT ) return 'int' ;
if ( typeId === Setting . TYPE _STRING ) return 'string' ;
if ( typeId === Setting . TYPE _BOOL ) return 'bool' ;
2017-11-12 19:02:20 +02:00
if ( typeId === Setting . TYPE _ARRAY ) return 'array' ;
2017-11-14 20:02:58 +02:00
if ( typeId === Setting . TYPE _OBJECT ) return 'object' ;
2017-10-20 00:02:13 +02:00
}
2019-01-28 01:15:56 +02:00
static groupMetadatasBySections ( metadatas ) {
2020-03-14 01:46:14 +02:00
const sections = [ ] ;
2019-01-28 01:15:56 +02:00
const generalSection = { name : 'general' , metadatas : [ ] } ;
const nameToSections = { } ;
nameToSections [ 'general' ] = generalSection ;
sections . push ( generalSection ) ;
for ( let i = 0 ; i < metadatas . length ; i ++ ) {
const md = metadatas [ i ] ;
if ( ! md . section ) {
generalSection . metadatas . push ( md ) ;
} else {
if ( ! nameToSections [ md . section ] ) {
nameToSections [ md . section ] = { name : md . section , metadatas : [ ] } ;
sections . push ( nameToSections [ md . section ] ) ;
}
nameToSections [ md . section ] . metadatas . push ( md ) ;
}
}
2019-07-29 15:43:53 +02:00
return sections ;
2019-01-28 01:15:56 +02:00
}
static sectionNameToLabel ( name ) {
if ( name === 'general' ) return _ ( 'General' ) ;
if ( name === 'sync' ) return _ ( 'Synchronisation' ) ;
if ( name === 'appearance' ) return _ ( 'Appearance' ) ;
if ( name === 'note' ) return _ ( 'Note' ) ;
2019-04-02 18:14:48 +02:00
if ( name === 'plugins' ) return _ ( 'Plugins' ) ;
2019-01-28 01:15:56 +02:00
if ( name === 'application' ) return _ ( 'Application' ) ;
2019-05-06 22:35:29 +02:00
if ( name === 'revisionService' ) return _ ( 'Note History' ) ;
2019-09-11 01:53:01 +02:00
if ( name === 'encryption' ) return _ ( 'Encryption' ) ;
if ( name === 'server' ) return _ ( 'Web Clipper' ) ;
return name ;
}
2020-04-10 19:33:09 +02:00
static sectionDescription ( name ) {
if ( name === 'plugins' ) return _ ( 'These plugins enhance the Markdown renderer with additional features. Please note that, while these features might be useful, they are not standard Markdown and thus most of them will only work in Joplin. Additionally, some of them are *incompatible* with the WYSIWYG editor. If you open a note that uses one of these plugins in that editor, you will lose the plugin formatting. It is indicated below which plugins are compatible or not with the WYSIWYG editor.' ) ;
if ( name === 'general' ) return _ ( 'Notes and settings are stored in: %s' , toSystemSlashes ( this . value ( 'profileDir' ) , process . platform ) ) ;
return '' ;
}
2019-09-13 00:16:42 +02:00
static sectionNameToIcon ( name ) {
2019-09-11 01:53:01 +02:00
if ( name === 'general' ) return 'fa-sliders' ;
if ( name === 'sync' ) return 'fa-refresh' ;
if ( name === 'appearance' ) return 'fa-pencil' ;
if ( name === 'note' ) return 'fa-file-text-o' ;
if ( name === 'plugins' ) return 'fa-puzzle-piece' ;
if ( name === 'application' ) return 'fa-cog' ;
if ( name === 'revisionService' ) return 'fa-archive-org' ;
if ( name === 'encryption' ) return 'fa-key-modern' ;
if ( name === 'server' ) return 'fa-hand-scissors-o' ;
2019-01-28 01:15:56 +02:00
return name ;
}
2019-02-23 17:53:14 +02:00
static appTypeToLabel ( name ) {
// Not translated for now because only used on Welcome notes (which are not translated)
if ( name === 'cli' ) return 'CLI' ;
return name [ 0 ] . toUpperCase ( ) + name . substr ( 1 ) . toLowerCase ( ) ;
}
2017-05-12 22:17:23 +02:00
}
2017-07-25 23:55:26 +02:00
Setting . TYPE _INT = 1 ;
Setting . TYPE _STRING = 2 ;
Setting . TYPE _BOOL = 3 ;
2017-11-12 19:02:20 +02:00
Setting . TYPE _ARRAY = 4 ;
2017-11-14 20:02:58 +02:00
Setting . TYPE _OBJECT = 5 ;
2019-12-13 02:40:58 +02:00
Setting . TYPE _BUTTON = 6 ;
2017-07-25 23:55:26 +02:00
2017-07-31 22:51:24 +02:00
Setting . THEME _LIGHT = 1 ;
Setting . THEME _DARK = 2 ;
2020-02-12 01:32:15 +02:00
Setting . THEME _OLED _DARK = 22 ;
2019-07-21 18:27:42 +02:00
Setting . THEME _SOLARIZED _LIGHT = 3 ;
Setting . THEME _SOLARIZED _DARK = 4 ;
2019-10-01 13:23:32 +02:00
Setting . THEME _DRACULA = 5 ;
2019-10-30 11:55:45 +02:00
Setting . THEME _NORD = 6 ;
2020-04-07 11:23:40 +02:00
Setting . THEME _ARITIM _DARK = 7 ;
2017-07-31 22:51:24 +02:00
2019-09-17 22:32:00 +02:00
Setting . FONT _DEFAULT = 0 ;
Setting . FONT _MENLO = 1 ;
Setting . FONT _COURIER _NEW = 2 ;
Setting . FONT _AVENIR = 3 ;
Setting . FONT _MONOSPACE = 4 ;
2019-10-31 10:42:17 +02:00
Setting . LAYOUT _ALL = 0 ;
Setting . LAYOUT _EDITOR _VIEWER = 1 ;
Setting . LAYOUT _EDITOR _SPLIT = 2 ;
Setting . LAYOUT _VIEWER _SPLIT = 3 ;
2020-03-10 01:24:57 +02:00
Setting . LAYOUT _SPLIT _WYSIWYG = 4 ;
2019-10-31 10:42:17 +02:00
2019-07-29 15:43:53 +02:00
Setting . DATE _FORMAT _1 = 'DD/MM/YYYY' ;
2017-11-28 20:02:54 +02:00
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' ;
2018-04-19 17:03:16 +02:00
Setting . DATE _FORMAT _6 = 'DD.MM.YYYY' ;
2020-01-26 19:26:50 +02:00
Setting . DATE _FORMAT _7 = 'YYYY.MM.DD' ;
2017-11-28 20:02:54 +02:00
Setting . TIME _FORMAT _1 = 'HH:mm' ;
Setting . TIME _FORMAT _2 = 'h:mm A' ;
2020-03-14 02:52:28 +02:00
Setting . SHOULD _REENCRYPT _NO = 0 ; // Data doesn't need to be re-encrypted
Setting . SHOULD _REENCRYPT _YES = 1 ; // Data should be re-encrypted
Setting . SHOULD _REENCRYPT _NOTIFIED = 2 ; // Data should be re-encrypted, and user has been notified
2020-03-13 19:42:50 +02:00
2019-12-13 02:40:58 +02:00
Setting . custom _css _files = {
JOPLIN _APP : 'userchrome.css' ,
RENDERED _MARKDOWN : 'userstyle.css' ,
} ;
2017-06-24 20:51:43 +02:00
// Contains constants that are set by the application and
// cannot be modified by the user:
Setting . constants _ = {
2017-10-30 23:29:36 +02:00
env : 'SET_ME' ,
isDemo : false ,
appName : 'joplin' ,
appId : 'SET_ME' , // Each app should set this identifier
appType : 'SET_ME' , // 'cli' or 'mobile'
2019-05-11 12:46:13 +02:00
resourceDirName : '' ,
2017-10-30 23:29:36 +02:00
resourceDir : '' ,
profileDir : '' ,
2019-07-20 23:13:10 +02:00
templateDir : '' ,
2017-10-30 23:29:36 +02:00
tempDir : '' ,
2019-12-17 19:06:55 +02:00
flagOpenDevTools : false ,
2019-10-10 23:23:11 +02:00
syncVersion : 1 ,
2019-07-29 15:43:53 +02:00
} ;
2017-06-24 20:51:43 +02:00
2018-02-15 19:12:09 +02:00
Setting . autoSaveEnabled = true ;
2018-11-08 00:37:13 +02:00
module . exports = Setting ;