Files
nix/hosts/common/user/configs/gui/obsidian/options.nix
Nikolaos Karaolidis bccc8d343b Add obsidian hotkeys
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2024-07-17 16:58:20 +01:00

499 lines
15 KiB
Nix

{
config,
pkgs,
lib,
...
}:
let
cfg = config.programs.obsidian;
corePlugins = [
"audio-recorder"
"backlink"
"bookmarks"
"canvas"
"command-palette"
"daily-notes"
"editor-status"
"file-explorer"
"file-recovery"
"global-search"
"graph"
"markdown-importer"
"note-composer"
"outgoing-link"
"outline"
"page-preview"
"properties"
"publish"
"random-note"
"slash-command"
"slides"
"switcher"
"sync"
"tag-pane"
"templates"
"word-count"
"workspaces"
"zk-prefixer"
];
toCssName = path: lib.strings.removeSuffix ".css" (builtins.baseNameOf path);
in
{
options.programs.obsidian =
with lib;
with types;
let
checkCssPath = path: lib.filesystem.pathIsRegularFile path && lib.strings.hasSuffix ".css" path;
cssSnippetsOptions =
{ config, ... }:
{
options = {
enable = mkOption {
type = bool;
default = true;
description = "Whether to enable the snippet.";
};
name = mkOption {
type = str;
defaultText = literalExpression "lib.strings.removeSuffix \".css\" (builtins.baseNameOf source)";
description = "Name of the snippet.";
};
source = mkOption {
type = nullOr (addCheck path checkCssPath);
description = "Path of the source file.";
default = null;
};
text = mkOption {
type = nullOr str;
description = "Text of the file.";
default = null;
};
};
config.name = mkDefault (toCssName config.source);
};
pluginsExtraFilesOptions =
{ name, config, ... }:
{
options = {
source = mkOption {
type = nullOr path;
description = "Path of the source file or directory.";
default = null;
};
text = mkOption {
type = nullOr str;
description = "Text of the file.";
default = null;
};
target = mkOption {
type = str;
defaultText = literalExpression "name";
description = "Path to target relative to the plugin directory.";
};
};
config.target = mkDefault name;
};
pluginsOptions =
{ config, ... }:
{
options = {
enable = mkOption {
type = bool;
default = true;
description = "Whether to enable the plugin.";
};
pkg = mkOption {
type = package;
description = "The plugin package.";
};
extraFiles = mkOption {
type = attrsOf (submodule pluginsExtraFilesOptions);
description = "Additional files to include in the plugin directory.";
default = { };
};
};
};
themesOptions =
{ config, ... }:
{
options = {
enable = mkOption {
type = bool;
default = true;
description = "Whether to set the theme as active.";
};
pkg = mkOption {
type = package;
description = "The theme package.";
};
};
};
hotkeysOptions =
{ config, ... }:
{
options = {
modifiers = mkOption {
type = listOf str;
description = "The hotkey modifiers.";
default = [ ];
};
key = mkOption {
type = str;
description = "The hotkey.";
};
};
};
in
{
enable = mkEnableOption "obsidian";
package = mkPackageOption pkgs "obsidian" { };
sharedSettings = {
app = mkOption {
description = "Settings to write to app.json.";
type = raw;
default = { };
};
appearance = mkOption {
description = "Settings to write to appearance.json.";
type = raw;
default = { };
};
corePlugins = mkOption {
description = "Core plugins to activate.";
type = raw;
default = [
"backlink"
"bookmarks"
"canvas"
"command-palette"
"daily-notes"
"editor-status"
"file-explorer"
"file-recovery"
"global-search"
"graph"
"note-composer"
"outgoing-link"
"outline"
"page-preview"
"switcher"
"tag-pane"
"templates"
"word-count"
];
};
cssSnippets = mkOption {
description = "CSS snippets to install.";
type = raw;
default = [ ];
};
plugins = mkOption {
description = "Community plugins to install and activate.";
type = raw;
default = [ ];
};
themes = mkOption {
description = "Themes to install.";
type = raw;
default = [ ];
};
hotkeys = mkOption {
description = "Hotkeys to configure.";
type = raw;
default = { };
};
};
vaults = mkOption {
description = "List of vaults to create.";
type = attrsOf (
submodule (
{ name, config, ... }:
{
options = {
enable = mkOption {
type = bool;
default = true;
description = "Whether this vault should be generated.";
};
target = mkOption {
type = str;
defaultText = literalExpression "name";
description = "Path to target vault relative to the user's {env}`HOME`.";
};
settings = {
app = mkOption {
description = "Settings to write to app.json.";
type = attrsOf anything;
default = cfg.sharedSettings.app;
};
appearance = mkOption {
description = "Settings to write to appearance.json.";
type = attrsOf anything;
default = cfg.sharedSettings.appearance;
};
corePlugins = mkOption {
description = "Core plugins to activate.";
type = listOf (enum corePlugins);
default = cfg.sharedSettings.corePlugins;
};
cssSnippets = mkOption {
description = "CSS snippets to install.";
type = listOf (either (addCheck path checkCssPath) (submodule cssSnippetsOptions));
default = cfg.sharedSettings.cssSnippets;
};
plugins = mkOption {
description = "Community plugins to install and activate.";
type = listOf (either package (submodule pluginsOptions));
default = cfg.sharedSettings.plugins;
};
themes = mkOption {
description = "Themes to install.";
type = listOf (either package (submodule themesOptions));
default = cfg.sharedSettings.themes;
};
hotkeys = mkOption {
description = "Hotkeys to configure.";
type = attrsOf (listOf (submodule hotkeysOptions));
default = cfg.sharedSettings.hotkeys;
};
};
};
config.target = mkDefault name;
}
)
);
default = { };
};
};
config =
let
vaults = builtins.filter (vault: vault.enable == true) (builtins.attrValues cfg.vaults);
toPkg = item: if item ? pkg then item.pkg else item;
isEnabled = item: if item ? enable then item.enable else true;
getCssName = item: if builtins.isAttrs item then item.name else toCssName item;
getManifest =
item:
let
manifest = builtins.fromJSON (builtins.readFile "${toPkg item}/manifest.json");
in
manifest.id or manifest.name;
in
lib.mkIf cfg.enable {
home = {
packages = [ cfg.package ];
file =
let
mkApp = vault: {
name = "${vault.target}/.obsidian/app.json";
value = {
source = (pkgs.formats.json { }).generate "app.json" vault.settings.app;
};
};
mkAppearance = vault: {
name = "${vault.target}/.obsidian/appearance.json";
value =
let
enabledCssSnippets = builtins.filter isEnabled vault.settings.cssSnippets;
activeTheme =
lib.lists.findSingle isEnabled null (throw "Only one theme can be enabled at a time.")
vault.settings.themes;
in
{
source = (pkgs.formats.json { }).generate "appearance.json" (
vault.settings.appearance
// {
enabledCssSnippets = builtins.map getCssName enabledCssSnippets;
}
// lib.attrsets.optionalAttrs (activeTheme != null) { cssTheme = getManifest activeTheme; }
);
};
};
mkCorePlugins = vault: [
{
name = "${vault.target}/.obsidian/core-plugins.json";
value = {
source = (pkgs.formats.json { }).generate "core-plugins.json" vault.settings.corePlugins;
};
}
{
name = "${vault.target}/.obsidian/core-plugins-migration.json";
value = {
source = (pkgs.formats.json { }).generate "core-plugins-migration.json" (
builtins.listToAttrs (
builtins.map (plugin: {
name = plugin;
value = builtins.elem plugin vault.settings.corePlugins;
}) vault.settings.corePlugins
)
);
};
}
];
mkCommunityPlugins =
vault:
let
enabledPlugins = builtins.filter isEnabled vault.settings.plugins;
in
[
{
name = "${vault.target}/.obsidian/community-plugins.json";
value = {
source = (pkgs.formats.json { }).generate "community-plugins.json" (
builtins.map getManifest enabledPlugins
);
};
}
]
/*
We can't do the following since plugins often write files in their directories,
and symlinking the entire folder does not give us write permissions.
builtins.map (plugin: {
name = "${vault.target}/.obsidian/plugins/${getManifestId plugin}";
value = { source = plugin; };
}) vault.settings.plugins;
This is why we do a double loop over plugins and their files.
*/
++ builtins.map (
plugin:
let
pkg = toPkg plugin;
files = builtins.attrNames (builtins.readDir pkg);
in
builtins.map (file: {
name = "${vault.target}/.obsidian/plugins/${getManifest plugin}/${file}";
value = {
source = "${pkg}/${file}";
};
}) files
) vault.settings.plugins
++ builtins.map (
plugin:
builtins.map (file: {
name = "${vault.target}/.obsidian/plugins/${getManifest plugin}/${file.target}";
value = if file.source != null then { inherit (file) source; } else { inherit (file) text; };
}) (builtins.attrValues (plugin.extraFiles or { }))
) vault.settings.plugins;
mkCssSnippets =
vault:
builtins.map (snippet: {
name = "${vault.target}/.obsidian/snippets/${getCssName snippet}.css";
value =
if snippet ? source || snippet ? text then
if snippet.source != null then { inherit (snippet) source; } else { inherit (snippet) text; }
else
{ source = snippet; };
}) vault.settings.cssSnippets;
mkThemes =
vault:
builtins.map (theme: {
name = "${vault.target}/.obsidian/themes/${getManifest theme}";
value = {
source = toPkg theme;
};
}) vault.settings.themes;
mkHotkeys = vault: {
name = "${vault.target}/.obsidian/hotkeys.json";
value = {
source = (pkgs.formats.json { }).generate "hotkeys.json" vault.settings.hotkeys;
};
};
in
builtins.listToAttrs (
lib.lists.flatten (
builtins.map (vault: [
(mkApp vault)
(mkAppearance vault)
(mkCorePlugins vault)
(mkCommunityPlugins vault)
(mkCssSnippets vault)
(mkThemes vault)
(mkHotkeys vault)
]) vaults
)
);
};
xdg.configFile."obsidian/obsidian.json".source = (pkgs.formats.json { }).generate "obsidian.json" {
vaults = builtins.listToAttrs (
builtins.map (vault: {
name = builtins.hashString "md5" vault.target;
value = {
path = "${config.home.homeDirectory}/${vault.target}";
} // (lib.attrsets.optionalAttrs ((builtins.length vaults) == 1) { open = true; });
}) vaults
);
updateDisabled = true;
};
assertions = [
{
assertion = builtins.all (
vault:
builtins.all (
snippet: (!snippet ? source && !snippet ? text) || (snippet.source == null || snippet.text == null)
) vault.settings.cssSnippets
) (builtins.attrValues cfg.vaults);
message = "Only one of `source` and `text` must be set";
}
{
assertion = builtins.all (
vault:
builtins.all (
plugin:
builtins.all (file: file.source == null || file.text == null) (
builtins.attrValues (plugin.extraFiles or { })
)
) vault.settings.plugins
) (builtins.attrValues cfg.vaults);
message = "Only one of `source` and `text` must be set";
}
];
};
}