dotfiles/home/programs/office/obsidian/default.nix

262 lines
9.2 KiB
Nix

{ config
, lib
, pkgs
, ...
}:
let
# Shared app.json defaults. The obsidian module merges `settings` per
# top-level key (app/appearance/...), NOT deeply — a vault that sets
# `settings.app` replaces this whole block, so reuse it explicitly.
appDefaults = {
defaultViewMode = "preview";
tabSize = 2;
vimMode = true;
focusNewTab = true;
propertiesInDocument = "visible";
foldHeading = true;
foldIndent = true;
rightToLeft = false;
spellcheck = true;
autoPairBrackets = true;
autoPairMarkdown = true;
smartIndentList = true;
useTab = true;
autoConvertHtml = true;
promptDelete = false;
trashOption = "local";
alwaysUpdateLinks = false;
newFileLocation = "current";
newLinkFormat = "shortest";
useMarkdownLinks = false;
showUnsupportedFiles = true;
};
website = "${config.home.homeDirectory}/development/bruvduroiu/website";
blogsFolder = "${config.home.homeDirectory}/Documents/Keep/4 - Blogs";
# Single action: mirror the vault's `4 - Blogs` folder into the Hugo repo's content/.
# Everything else (build, commit, push, local preview) is handled manually outside
# Obsidian. rsync --delete gives true mirror semantics (deletions propagate); cp -a
# would leave ghosts of deleted posts behind.
blog-sync = pkgs.writeShellApplication {
name = "blog-sync";
runtimeInputs = with pkgs; [ rsync ];
text = ''
vault="${blogsFolder}"
repo="${website}"
# Safety: refuse to mirror an unseeded vault rsync --delete would otherwise
# wipe every post in the repo.
if [ ! -d "$vault/blog" ] || [ ! -f "$vault/_index.md" ]; then
echo "blog-sync: '4 - Blogs' looks unseeded (missing blog/ or _index.md); aborting." >&2
exit 1
fi
rsync -a --delete --exclude='.obsidian' "$vault/" "$repo/content/"
echo "blog-sync: mirrored 4 - Blogs -> $repo/content"
'';
};
# One Shell-commands `data.json` command entry. Mirrors the plugin's
# newShellCommandConfiguration() exactly — the module writes data.json as a
# read-only store symlink, and Shell commands uses the loaded file as-is (it only
# merges defaults to backfill `debug`/`settings_version`), so every field a current
# install expects must be present or command registration can throw.
mkShellCommand = id: alias: command: {
inherit id alias;
platform_specific_commands.default = command;
shells = { };
icon = null;
confirm_execution = false;
ignore_error_codes = [ ];
input_contents.stdin = null;
output_handlers = {
stdout = { handler = "ignore"; convert_ansi_code = true; };
stderr = { handler = "notification"; convert_ansi_code = true; };
};
output_wrappers = { stdout = null; stderr = null; };
output_channel_order = "stdout-first";
output_handling_mode = "buffered";
execution_notification_mode = null;
events = { };
debounce = null;
command_palette_availability = "enabled";
preactions = [ ];
variable_default_values = { };
};
# Shell commands' built-in shells hardcode /bin/bash, /bin/dash, /bin/zsh in
# getBinaryPath() and spawn THAT (the configured shell string only drives matching) —
# none of those exist on NixOS, which provides only /bin/sh. So a built-in shell is
# unusable here by construction (an unset shell falls back to $SHELL=fish, which the
# plugin also doesn't support). Define a custom shell pinned to /bin/sh instead.
# Mirrors CustomShellModel.getDefaultConfiguration(); our command is an absolute-path
# binary, so a POSIX sh is all that is needed.
nixShellId = "nixos-binsh";
nixShell = {
id = nixShellId;
name = "NixOS /bin/sh";
description = "POSIX sh via /bin/sh built-in shells hardcode nonexistent /bin/bash and /bin/dash on NixOS.";
binary_path = "/bin/sh";
shell_arguments = [ "-c" "{{shell_command_content}}" ];
host_platform = "linux";
host_platform_configurations = { };
shell_platform = null;
escaper = "UnixShell";
path_translator = null;
shell_command_wrapper = null;
shell_command_test = null;
};
# Full SC_MainSettings default object (settings_version pinned to the packaged
# plugin version, so no migration runs against the read-only file) + our command.
shellcommandsSettings = {
settings_version = "0.23.0";
debug = false;
obsidian_command_palette_prefix = "Execute: ";
preview_variables_in_command_palette = true;
show_autocomplete_menu = true;
working_directory = "";
default_shells = { linux = nixShellId; };
environment_variable_path_augmentations = { };
show_installation_warnings = true;
error_message_duration = 20;
notification_message_duration = 10;
execution_notification_mode = "disabled";
output_channel_clipboard_also_outputs_to_notification = true;
output_channel_notification_decorates_output = true;
enable_events = true;
approve_modals_by_pressing_enter_key = true;
command_palette.re_execute_last_shell_command = {
enabled = true;
prefix = "Re-execute: ";
};
max_visible_lines_in_shell_command_fields = false;
shell_commands = [
(mkShellCommand "blog-sync" "Blog: sync vault -> repo content" "${blog-sync}/bin/blog-sync")
];
prompts = [ ];
builtin_variables = { };
custom_variables = [ ];
custom_variables_notify_changes_via = {
obsidian_uri = true;
output_assignment = true;
};
custom_shells = [ nixShell ];
output_wrappers = [ ];
};
in
{
# Scripts, templates, and bases are stored here as version-controlled backup.
# They live in the vault directly (synced via Syncthing).
# blog-sync is also exposed on PATH for use outside Obsidian.
home.packages = [ blog-sync ];
programs.obsidian = {
enable = true;
package = pkgs.obsidian;
vaults = {
Keep = {
enable = true;
target = "Documents/Keep";
settings = {
app = appDefaults // {
attachmentFolderPath = "9 - Attachments";
# All new notes land in 5 - Main Notes. Blogs start there too but the Blog Post
# template immediately tp.file.move()s them into 4 - Blogs/blog/<slug>/.
newFileLocation = "folder";
newFileFolderPath = "5 - Main Notes";
};
# Declarative custom hotkeys (.obsidian/hotkeys.json). Shell-commands command
# ids are `obsidian-shellcommands:shell-command-<id>`; Templater registers each
# enabled template-hotkey as `templater-obsidian:<vault-relative path>`.
hotkeys = {
"obsidian-shellcommands:shell-command-blog-sync" = [
{ modifiers = [ "Mod" "Shift" ]; key = "P"; }
];
"templater-obsidian:9 - Templates/Blog Post.md" = [
{ modifiers = [ "Mod" "Shift" ]; key = "B"; }
];
};
corePlugins = [
"backlink"
"bases"
"bookmarks"
"canvas"
"command-palette"
"editor-status"
"file-explorer"
"file-recovery"
"global-search"
"graph"
"note-composer"
"outgoing-link"
"outline"
"page-preview"
"slash-command"
"switcher"
"tag-pane"
"templates"
"word-count"
];
communityPlugins = [
{
pkg = pkgs.callPackage ./plugins/templater.nix { };
settings = {
trigger_on_file_creation = true;
auto_jump_to_cursor = true;
enable_system_commands = true;
syntax_highlighting = true;
shell_path = "fish";
command_palette = true;
templates_folder = "9 - Templates";
user_scripts_folder = "9 - Scripts";
# Register the Blog Post template as a hotkeyable command.
enabled_templates_hotkeys = [ "9 - Templates/Blog Post.md" ];
};
}
{
pkg = pkgs.callPackage ./plugins/dataview.nix { };
settings = {
enableDataviewJs = true;
enableInlineDataviewJs = true;
taskCompletionUseEmojiShorthand = true;
taskCompletionTracking = true;
defaultDateFormat = "dd/MM/yyyy";
defaultDateTimeFormat = "HH:mm - dd/MM/yyyy";
};
}
{
pkg = pkgs.callPackage ./plugins/maps.nix { };
settings = {
tileSets = [
{
id = "protomaps";
name = "Protomaps";
lightTiles = "https://tiles.openfreemap.org/styles/liberty";
darkTiles = "https://tiles.openfreemap.org/styles/dark";
}
];
};
}
{
# Single rsync command (see blog-sync above).
pkg = pkgs.callPackage ./plugins/shellcommands.nix { };
settings = shellcommandsSettings;
}
];
};
};
};
defaultSettings = {
appearance = {
baseFontSize = lib.mkForce 16;
};
app = appDefaults;
};
};
}