2024-02-18 22:57:35 +01:00
{ config , lib , pkgs , . . . }:
let
cfg = config . programs . joplin-desktop ;
jsonFormat = pkgs . formats . json { } ;
# config path is the same for linux and mac
2024-07-17 12:58:53 +02:00
configPath = " ${ config . xdg . configHome } / j o p l i n - d e s k t o p " ;
# toJoplinSettings receives cfg as input and creates the content of the joplin-desktop/settings.json file
toJoplinSettings = settings :
( jsonFormat . generate " j o p l i n - s e t t i n g s . j s o n "
( lib . attrsets . filterAttrsRecursive ( n : v : v != null ) ( {
### General
" e d i t o r " = settings . general . editor ;
" l o c a l e " = settings . general . language ;
### Sync
" s y n c . i n t e r v a l " = {
" " = null ;
" d i s a b l e d " = 0 ;
" 5 m " = 300 ;
" 1 0 m " = 600 ;
" 3 0 m " = 1800 ;
" 1 h " = 3600 ;
" 1 2 h " = 43200 ;
" 1 d " = 86400 ;
} . ${ settings . sync . interval } ;
### Appearance
" t h e m e " = {
" " = null ;
" l i g h t " = 1 ;
" d a r k " = 2 ;
" s o l a r i s e d - l i g h t " = 3 ;
" s o l a r i s e d - d a r k " = 4 ;
" d r a c u l a " = 5 ;
" n o r d " = 6 ;
" a r i t i m - d a r k " = 7 ;
" o l e d - d a r k " = 22 ;
} . ${ settings . appearance . theme } ;
" t h e m e A u t o D e t e c t " = settings . appearance . autoDetectTheme ;
### Note
" i m a g e R e s i z i n g " = settings . note . resizeLargeImages ;
" n e w T o d o F o c u s " = settings . note . newTodoFocus ;
" n e w N o t e F o c u s " = settings . note . newTodoFocus ;
" t r a c k L o c a t i o n " = settings . note . saveGeoLocation ;
" e d i t o r . a u t o M a t c h i n g B r a c e s " = settings . note . autoPairBraces ;
}
### Markdown
# map all values; replaces "markdown.plugin.softbreaks" = settings.markdown.softbreaks;
// ( lib . attrsets . concatMapAttrs
( name : value : { " m a r k d o w n . p l u g i n . ${ name } " = value ; } )
settings . markdown )
### Application
### Encryption
### Web Clipper
### Keyboard Shortcuts
// settings . extraConfig ) ) ) ;
# This creates the content of the joplin-desktop/<profile-name>/settings.json file
toProfileSettings = settings :
( jsonFormat . generate " p r o f i l e - s e t t i n g s . j s o n "
( lib . attrsets . filterAttrsRecursive ( n : v : v != null ) {
### Sync
" s y n c . t a r g e t " = {
" " = null ;
" n o n e " = 0 ;
" f i l e - s y s t e m " = 2 ;
" o n e d r i v e " = 3 ;
" n e x t c l o u d " = 5 ;
" w e b d a v " = 6 ;
" d r o p b o x " = 7 ;
" s 3 " = 8 ;
" j o p l i n - s e r v e r " = 9 ;
" j o p l i n - c l o u d " = 10 ;
} . ${ settings . sync . target } ;
### Note History
" r e v i s i o n S e r v i c e . e n a b l e d " = settings . noteHistory . enable ;
" r e v i s i o n S e r v i c e . t t l D a y s " = settings . noteHistory . historyDuration ;
} // settings . extraConfig ) ) ;
# This creates the content of the joplin-desktop/profiles.json file
toJoplinProfiles = profiles :
( jsonFormat . generate " p r o f i l e s . j s o n " ( {
" p r o f i l e s " = ( builtins . map ( name : {
" n a m e " = name ;
" i d " = profiles . ${ name } . id ;
} ) ( builtins . attrNames profiles ) ) ;
} ) ) ;
2024-02-18 22:57:35 +01:00
in {
2024-03-13 18:11:23 +01:00
meta . maintainers = [ lib . hm . maintainers . zorrobert ] ;
2024-02-18 22:57:35 +01:00
options . programs . joplin-desktop = {
enable = lib . mkEnableOption " j o p l i n - d e s k t o p " ;
package = lib . mkPackageOption pkgs " j o p l i n - d e s k t o p " { } ;
2024-07-17 12:58:53 +02:00
# This could be implemented in the future if needed
# useNixStore = lib.mkOption {
# type = lib.types.bool;
# default = false;
# description = ''
# There are two ways to configure Joplin, #1 is the default:
# #1: Use jq and printf to generate and write to .config/joplin-desktop/settings.json.
# #2: Use home.file to create the config file in the Nix Store and link it to .config/joplin-desktop/settings.json.
#
# Both methods have advantages and drawbacks:
# #1: This method still allows the user to change settings via the Joplin GUI, which is good for usability. This is also necessary to find out what to put into the extraConfig option to change a setting not covered by this module. The problem is that the file is only written when options in home-manager are changed, but not when changes are made via the GUI, potentially leading to inconsistencies across systems.
# #2: Because the Nix Store is read-only, settings can't be changed via the Joplin GUI. The advantage is that this ensures that every system has the same config file because it is only written by home-manager. The problem is that since Joplin itself can't modify the config file, it can't store things like "api.token", "$schema" or "ui.layout", which could break things.
#
# Because of the potentially side effects that a read-only config file could have on Joplin, #1 is the default. Still, there may be cases where #2 could be useful.
# '';
# };
2024-02-18 22:57:35 +01:00
extraConfig = lib . mkOption {
type = lib . types . attrs ;
default = { } ;
example = {
" n e w N o t e F o c u s " = " t i t l e " ;
" m a r k d o w n . p l u g i n . m a r k " = true ;
2024-07-17 12:58:53 +02:00
" s y n c . i n t e r v a l " = 600 ;
2024-02-18 22:57:35 +01:00
} ;
description = ''
2024-07-17 12:58:53 +02:00
Use this to add other options to the global Joplin config file . Settings are
2024-02-18 22:57:35 +01:00
written in JSON , so ` " s y n c . i n t e r v a l " : 600 ` would be written as
2024-07-17 12:58:53 +02:00
` " s y n c . i n t e r v a l " = 600 ; ` .
2024-02-18 22:57:35 +01:00
'' ;
} ;
### General
general = {
editor = lib . mkOption {
type = lib . types . nullOr lib . types . str ;
default = null ;
example = " k a t e " ;
description = ''
The editor command ( may include arguments ) that will be used to open a
note . If none is provided Joplin will try to auto-detect the default
editor .
'' ;
} ;
2024-07-17 12:58:53 +02:00
language = lib . mkOption {
type = lib . types . nullOr lib . types . str ;
default = null ;
example = " e n _ G B " ;
description = " T h e l a n g u a g e o f t h e J o p l i n A p p l i c a t i o n . " ;
} ;
2024-02-18 22:57:35 +01:00
} ;
### Sync
sync = {
2024-07-17 12:58:53 +02:00
interval = lib . mkOption {
type =
lib . types . enum [ " " " d i s a b l e d " " 5 m " " 1 0 m " " 3 0 m " " 1 h " " 1 2 h " " 1 d " ] ;
default = " " ;
example = " 1 0 m " ;
description = " S e t t h e s y n c h r o n i s a t i o n i n t e r v a l . " ;
2024-02-18 22:57:35 +01:00
} ;
2024-07-17 12:58:53 +02:00
} ;
2024-02-18 22:57:35 +01:00
2024-07-17 12:58:53 +02:00
### Appearance
appearance = {
theme = lib . mkOption {
2024-04-04 21:49:02 +02:00
type = lib . types . enum [
2024-07-17 12:58:53 +02:00
" "
" l i g h t "
" d a r k "
" s o l a r i s e d - l i g h t "
" s o l a r i s e d - d a r k "
" d r a c u l a "
" n o r d "
" a r i t i m - d a r k "
" o l e d - d a r k "
2024-04-04 21:49:02 +02:00
] ;
2024-07-17 12:58:53 +02:00
default = " " ;
2024-02-18 22:57:35 +01:00
example = " 1 0 m " ;
2024-07-17 12:58:53 +02:00
description = " S e t t h e a p p l i c a t i o n t h e m e . " ;
} ;
autoDetectTheme = lib . mkOption {
type = lib . types . nullOr lib . types . bool ;
default = null ;
description = " A u t o m a t i c a l l y s w i t c h t h e m e t o m a t c h s y s t e m t h e m e . " ;
} ;
} ;
### Note
note = {
resizeLargeImages = lib . mkOption {
type = lib . types . enum [ null " a l w a y s R e s i z e " " a l w a y s A s k " " n e v e r R e s i z e " ] ;
default = null ;
description =
" S h r i n k l a r g e i m a g e s b e f o r e a d d i n g t h e m t o n o t e s t o s a v e s t o r a g e s p a c e . " ;
} ;
newTodoFocus = lib . mkOption {
type = lib . types . enum [ null " b o d y " " t i t l e " ] ;
default = null ;
description = " F o c u s b o d y o r t i t l e w h e n c r e a t i n g a n e w t o - d o . " ;
} ;
newNoteFocus = lib . mkOption {
type = lib . types . enum [ null " b o d y " " t i t l e " ] ;
default = null ;
description = " F o c u s b o d y o r t i t l e w h e n c r e a t i n g a n e w n o t e . " ;
} ;
saveGeoLocation = lib . mkOption {
type = lib . types . nullOr lib . types . bool ;
default = null ;
description = " W h e t h e r t o s a v e g e o - l o c a t i o n t o t h e n o t e . " ;
} ;
autoPairBraces = lib . mkOption {
type = lib . types . nullOr lib . types . bool ;
default = null ;
description =
" W h e t h e r t o a u t o - p a i r b r a c e s , p a r a n t h e s i s , q u o t a t i o n s , e t c . " ;
} ;
} ;
### Plugins (WIP)
# This is a WIP and not ready for release yet
#plugins = {
# installedPlugins = lib.mkOption {
# type = lib.types.listOf lib.types.str;
# default = [ ];
# example = [
# "com.s73ph4n.automate_notes"
# "com.github.marc0l92.joplin-plugin-drawio"
# "com.gitlab.BeatLink.joplin-plugin-agenda"
# ];
# description = ''
# A list of plugins to install.
# The full list of Plugins can be found here:
# https://github.com/joplin/plugins/blob/master/README.md#plugins
# The plugin name can be found in the download URL:
# https://github.com/joplin/plugins/raw/master/plugins/PLUGIN-NAME/plugin.jpl
# '';
# };
#};
### Markdown
# The markdown options are all booleans with default = null, the definitions can be shortened.
markdown = let
type = lib . types . nullOr lib . types . bool ;
default = null ;
in {
softbreaks = lib . mkOption {
inherit type default ;
description = " E n a b l e s o f t b r e a k s ( w y s i w y g : y e s ) " ;
} ;
typographer = lib . mkOption {
inherit type default ;
description = " E n a b l e t y p o g r a p h e r s u p p o r t ( w y s i w y g : y e s ) " ;
} ;
linkify = lib . mkOption {
inherit type default ;
description = " E n a b l e L i n k i f y ( w y s i w y g : y e s ) " ;
} ;
math = lib . mkOption {
inherit type default ;
description = " E n a b l e m a t h e x p r e s s i o n s ( w y s i w y g : y e s ) " ;
} ;
fountain = lib . mkOption {
inherit type default ;
description = " E n a b l e F o u n t a i n s u p p o r t ( w y s i w y g : y e s ) " ;
} ;
mermaid = lib . mkOption {
inherit type default ;
description = " E n a b l e M e r m a i d d i a g r a m s s u p p o r t ( w y s i w y g : y e s ) " ;
} ;
audioPlayer = lib . mkOption {
inherit type default ;
description = " E n a b l e a u d i o p l a y e r ( w y s i w y g : n o ) " ;
} ;
videoPlayer = lib . mkOption {
inherit type default ;
description = " E n a b l e v i d e o p l a y e r ( w y s i w y g : n o ) " ;
} ;
pdfViewer = lib . mkOption {
inherit type default ;
description = " E n a b l e P D F v i e w e r ( w y s i w y g : n o ) " ;
} ;
mark = lib . mkOption {
inherit type default ;
description = " E n a b l e = = m a r k = = s y n t a x ( w y s i w y g : y e s ) " ;
} ;
footnote = lib . mkOption {
inherit type default ;
description = " E n a b l e f o o t n o t e s ( w y s i w y g : n o ) " ;
} ;
toc = lib . mkOption {
inherit type default ;
description = " E n a b l e t a b l e o f c o n t e n t s e x t e n s i o n ( w y s i w y g : n o ) " ;
} ;
sub = lib . mkOption {
inherit type default ;
description = " E n a b l e ~ s u b ~ s y n t a x ( w y s i w y g : y e s ) " ;
} ;
sup = lib . mkOption {
inherit type default ;
description = " E n a b l e ^ s u p ^ s y n t a x ( w y s i w y g : y e s ) " ;
} ;
deflist = lib . mkOption {
inherit type default ;
description = " E n a b l e d e f l i s t s y n t a x ( w y s i w y g : n o ) " ;
} ;
abbr = lib . mkOption {
inherit type default ;
description = " E n a b l e a b b r e v i a t i o n s y n t a x ( w y s i w y g : n o ) " ;
} ;
emoji = lib . mkOption {
inherit type default ;
description = " E n a b l e m a r k d o w n e m o j i ( w y s i w y g : n o ) " ;
2024-02-18 22:57:35 +01:00
} ;
2024-07-17 12:58:53 +02:00
insert = lib . mkOption {
inherit type default ;
description = " E n a b l e + + i n s e r t + + s y n t a x ( w y s i w y g : y e s ) " ;
} ;
multitable = lib . mkOption {
inherit type default ;
description = " E n a b l e m u l t i m a r k d o w n t a b l e e x t e n s i o n ( w y s i w y g : n o ) " ;
} ;
} ;
### Application
### Encryption
### Web Clipper
### Keyboard Shortcuts
profiles = lib . mkOption {
default = { } ;
description = ''
Joplin supports creating multiple profiles . The settings in this set
are profile-specific , while others are shared between profiles .
See https://joplinapp.org/help/apps/profiles/
'' ;
type = lib . types . attrsOf ( lib . types . submodule ( { config , name , . . . }: {
options = {
name = lib . mkOption {
type = lib . types . str ;
default = name ;
description = " P r o f i l e n a m e . " ;
} ;
id = lib . mkOption {
type = lib . types . str ;
# The default profile has the name "Default" and needs the ID "default".
default = ( if name == " D e f a u l t " then
" d e f a u l t "
else
( builtins . substring 0 8 ( builtins . hashString " m d 5 " name ) ) ) ;
description = ''
The Profile ID .
This should be unique string of 8 characters per profile .
By default , the first 8 characters of the md5 hash
of the profile name are used .
'' ;
} ;
extraConfig = lib . mkOption {
type = lib . types . attrs ;
default = { } ;
example = {
" n e w N o t e F o c u s " = " t i t l e " ;
" m a r k d o w n . p l u g i n . m a r k " = true ;
} ;
description = ''
Use this to further modify the config file of this profile .
Settings are written in JSON , so ` " s y n c . t a r g e t " : 7 ` would be
written as ` " s y n c . t a r g e t " = " d r o p b o x " ; ` .
Note that if you add settings here that Joplin views as
" g l o b a l s e t t i n g s " ( see https://joplinapp.org/help/apps/profiles/ ) ,
they will not be applied in the profile .
Try to use the joplin-desktop . extraConfig option instead .
'' ;
} ;
### Sync
sync = {
target = lib . mkOption {
type = lib . types . enum [
" "
" n o n e "
" f i l e - s y s t e m "
" o n e d r i v e "
" n e x t c l o u d "
" w e b d a v "
" d r o p b o x "
" s 3 "
" j o p l i n - s e r v e r "
" j o p l i n - c l o u d "
] ;
default = " " ;
example = " d r o p b o x " ;
description = " W h a t i s t h e t y p e o f s y n c t a r g e t . " ;
} ;
} ;
### Note History (profile-specific)
noteHistory = {
enable = lib . mkOption {
type = lib . types . nullOr lib . types . bool ;
default = null ;
description = " E n a b l e n o t e h i s t o r y . " ;
} ;
historyDuration = lib . mkOption {
type = lib . types . nullOr lib . types . int ;
default = null ;
description = " K e e p N o t e H i s t o r y f o r ( d a y s ) " ;
} ;
} ;
} ;
} ) ) ;
2024-02-18 22:57:35 +01:00
} ;
} ;
config = lib . mkIf cfg . enable {
2024-07-17 12:58:53 +02:00
assertions = [ {
assertion = ( lib . attrsets . matchAttrs { Default = { id = " d e f a u l t " ; } ; }
cfg . profiles ) ;
message = ''
Joplin-Desktop : The profile ' Default' must exist and have an ID other than ' default' .
'' ;
} ] ;
2024-02-18 22:57:35 +01:00
home . packages = [ cfg . package ] ;
home . activation = {
2024-07-17 12:58:53 +02:00
createJoplinConfig = lib . hm . dag . entryAfter [ " l i n k G e n e r a t i o n " ]
( lib . concatStringsSep " \n " (
# write the global config file (including settings for the default profile)
[ ''
# Ensure that settings.json exists.
mkdir - p $ { configPath }
touch $ { configPath } /settings.json
# Config has to be written to temporary variable because jq cannot edit files in place.
config = " $ ( j q - s ' . [ 0 ] + . [ 1 ] + . [ 2 ] ' ${ configPath } / s e t t i n g s . j s o n ${
toJoplinSettings cfg
} $ { toProfileSettings cfg . profiles . Default } ) "
printf ' % s \ n' " $ c o n f i g " > $ { configPath } /settings.json
unset config
'' ]
# create the profiles and write the config (except for the default profile)
++ ( builtins . map ( name : ''
# create profile folder
mkdir - p $ { configPath } /profile- $ { cfg . profiles . ${ name } . id }
# create config file in every profile folder
touch $ { configPath } /profile- $ { cfg . profiles . ${ name } . id } /settings.json
# Config has to be written to temporary variable because jq cannot edit files in place.
config = " $ ( j q - s ' . [ 0 ] + . [ 1 ] ' ${ configPath } / p r o f i l e - ${
cfg . profiles . ${ name } . id
} /settings.json $ { toProfileSettings cfg . profiles . ${ name } } ) "
# write config to file
printf ' % s \ n' " $ c o n f i g " > $ { configPath } /profile- $ {
cfg . profiles . ${ name } . id
} /settings.json
unset config
'' ) ( l i b . l i s t s . r e m o v e " D e f a u l t " ( b u i l t i n s . a t t r N a m e s c f g . p r o f i l e s ) ) )
# add all declared profiles to profiles.json
++ [ ''
# create profiles.json
touch $ { configPath } /profiles.json
# Config has to be written to temporary variable because jq cannot edit files in place.
profiles = " $ ( j q - s ' . [ 0 ] + . [ 1 ] ' ${ configPath } / p r o f i l e s . j s o n ${
toJoplinProfiles cfg . profiles
} ) "
# write config to file
printf ' % s \ n' " $ p r o f i l e s " > $ { configPath } /profiles.json
unset profiles
'' ]
# download plugins (WIP)
#++ (builtins.map (plugin-name: ''
# ${pkgs.wget}/bin/wget --output-document ${configPath}/plugins/${plugin-name}.jpl https://github.com/joplin/plugins/raw/master/plugins/${plugin-name}/plugin.jpl
#'') cfg.plugins.installedPlugins)
) ) ;
2024-02-18 22:57:35 +01:00
} ;
} ;
}