-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
obsidian: (RFC) add module #6487
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
For reference, here is an example plugin package: { pkgs, ... }:
pkgs.stdenv.mkDerivation rec {
pname = "obsidian.plugins.tasks";
version = "7.15.0";
src = pkgs.fetchFromGitHub {
owner = "obsidian-tasks-group";
repo = "obsidian-tasks";
rev = version;
hash = "sha256-BF9ye4ocE6vZh+ChkmuLkQpNWtH425EX0EHQs+wbTZc=";
};
offlineCache = pkgs.fetchYarnDeps {
yarnLock = src + "/yarn.lock";
hash = "sha256-Tf1K048Ox+hImIfrdBWQHsiDe+3FGUQLFBcf/Bbbo1U=";
};
nativeBuildInputs = with pkgs; [
nodejs
yarnConfigHook
yarnBuildHook
npmHooks.npmInstallHook
];
installPhase = ''
mkdir -p $out
cp ./manifest.json $out/manifest.json
cp ./main.js $out/main.js
cp ./styles.css $out/styles.css
'';
} |
a82a14c to
6db31ab
Compare
dfdb0ac to
dd5d4e7
Compare
6db31ab to
65c21b0
Compare
|
+1 |
6266e0b to
4505481
Compare
khaneliman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM overall, just had a couple questions that I may have figured out while trying to review it..
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
I don't use stylix myself, I use https://github.com/InioX/matugen with a custom Nix theming module, but you can probably adapt what I have for stylix? I installed Minimal and Style Settings, and then I do all my theme configuration in style settings' data.json: .obsidian/plugins/obsidian-style-settings/data.json{
"minimal-style@@base@@light": "{{colors.surface.light.hex}}",
"minimal-style@@base@@dark": "{{colors.surface.dark.hex}}",
"minimal-style@@bg1@@light": "{{colors.surface.light.hex}}",
"minimal-style@@bg1@@dark": "{{colors.surface.dark.hex}}",
"minimal-style@@bg2@@light": "{{colors.surface_container.light.hex}}",
"minimal-style@@bg2@@dark": "{{colors.surface_container.dark.hex}}",
"minimal-style@@bg3@@light": "{{colors.surface_container_highest.light.hex}}",
"minimal-style@@bg3@@dark": "{{colors.surface_container_highest.dark.hex}}",
"minimal-style@@ui1@@light": "{{colors.outline_variant.light.hex}}",
"minimal-style@@ui1@@dark": "{{colors.outline_variant.dark.hex}}",
"minimal-style@@ui2@@light": "{{colors.outline.light.hex}}",
"minimal-style@@ui2@@dark": "{{colors.outline.dark.hex}}",
"minimal-style@@ui3@@light": "{{colors.primary.light.hex}}",
"minimal-style@@ui3@@dark": "{{colors.primary.dark.hex}}",
"minimal-style@@ax1@@light": "{{colors.primary.light.hex}}",
"minimal-style@@ax1@@dark": "{{colors.primary.dark.hex}}",
"minimal-style@@ax2@@light": "{{colors.on_primary_container.light.hex}}",
"minimal-style@@ax2@@dark": "{{colors.on_primary_container.dark.hex}}",
"minimal-style@@ax3@@light": "{{colors.primary.light.hex}}",
"minimal-style@@ax3@@dark": "{{colors.primary.dark.hex}}",
"minimal-style@@sp1@@light": "{{colors.on_primary_container.light.hex}}",
"minimal-style@@sp1@@dark": "{{colors.on_primary_container.dark.hex}}",
"minimal-style@@color-red@@light": "{{colors.red.light.hex}}",
"minimal-style@@color-red@@dark": "{{colors.red.dark.hex}}",
"minimal-style@@color-orange@@light": "{{colors.orange.light.hex}}",
"minimal-style@@color-orange@@dark": "{{colors.orange.dark.hex}}",
"minimal-style@@color-yellow@@light": "{{colors.yellow.light.hex}}",
"minimal-style@@color-yellow@@dark": "{{colors.yellow.dark.hex}}",
"minimal-style@@color-green@@light": "{{colors.green.light.hex}}",
"minimal-style@@color-green@@dark": "{{colors.green.dark.hex}}",
"minimal-style@@color-cyan@@light": "{{colors.cyan.light.hex}}",
"minimal-style@@color-cyan@@dark": "{{colors.cyan.dark.hex}}",
"minimal-style@@color-blue@@light": "{{colors.blue.light.hex}}",
"minimal-style@@color-blue@@dark": "{{colors.blue.dark.hex}}",
"minimal-style@@color-purple@@light": "{{colors.magenta.light.hex}}",
"minimal-style@@color-purple@@dark": "{{colors.magenta.dark.hex}}",
"minimal-style@@color-pink@@light": "{{colors.pink.light.hex}}",
"minimal-style@@color-pink@@dark": "{{colors.pink.dark.hex}}",
"minimal-style@@blockquote-color@@light": "{{colors.on_surface_variant.light.hex}}",
"minimal-style@@blockquote-color@@dark": "{{colors.on_surface_variant.dark.hex}}",
"minimal-style@@canvas-dot-pattern@@light": "{{colors.outline_variant.light.hex}}",
"minimal-style@@canvas-dot-pattern@@dark": "{{colors.outline_variant.dark.hex}}",
"minimal-style@@tag-color@@light": "{{colors.on_primary_container.light.hex}}",
"minimal-style@@tag-color@@dark": "{{colors.on_primary_container.dark.hex}}",
"minimal-style@@tag-background@@light": "{{colors.primary_container.light.hex}}",
"minimal-style@@tag-background@@dark": "{{colors.primary_container.dark.hex}}",
"minimal-style@@tag-background-hover@@light": "{{colors.on_primary.light.hex}}",
"minimal-style@@tag-background-hover@@dark": "{{colors.on_primary.dark.hex}}",
"minimal-style@@tx1@@light": "{{colors.on_surface.light.hex}}",
"minimal-style@@tx1@@dark": "{{colors.on_surface.dark.hex}}",
"minimal-style@@tx2@@light": "{{colors.on_surface_variant.light.hex}}",
"minimal-style@@tx2@@dark": "{{colors.on_surface_variant.dark.hex}}",
"minimal-style@@tx3@@light": "{{colors.outline.light.hex}}",
"minimal-style@@tx3@@dark": "{{colors.outline.dark.hex}}",
"minimal-style@@hl1@@light": "{{colors.primary_container.light.hex}}",
"minimal-style@@hl1@@dark": "{{colors.primary_container.dark.hex}}",
"minimal-style@@text-formattin@@light": "{{colors.outline_variant.light.hex}}",
"minimal-style@@text-formattin@@dark": "{{colors.outline_variant.dark.hex}}",
"minimal-style@@code-comment@@light": "{{colors.outline.light.hex}}",
"minimal-style@@code-comment@@dark": "{{colors.outline.dark.hex}}",
"minimal-style@@code-function@@light": "{{colors.functions.light.hex}}",
"minimal-style@@code-function@@dark": "{{colors.functions.dark.hex}}",
"minimal-style@@code-keyword@@light": "{{colors.keywords.light.hex}}",
"minimal-style@@code-keyword@@dark": "{{colors.keywords.dark.hex}}",
"minimal-style@@code-important@@light": "{{colors.info.light.hex}}",
"minimal-style@@code-important@@dark": "{{colors.info.dark.hex}}",
"minimal-style@@code-property@@light": "{{colors.properties.light.hex}}",
"minimal-style@@code-property@@dark": "{{colors.properties.dark.hex}}",
"minimal-style@@code-string@@light": "{{colors.strings.light.hex}}",
"minimal-style@@code-string@@dark": "{{colors.strings.dark.hex}}",
"minimal-style@@code-tag@@light": "{{colors.properties.light.hex}}",
"minimal-style@@code-tag@@dark": "{{colors.properties.dark.hex}}",
"minimal-style@@code-value@@light": "{{colors.numbers.light.hex}}",
"minimal-style@@code-value@@dark": "{{colors.numbers.dark.hex}}",
"minimal-style@@image-radius": "{{custom.radius}}",
"minimal-style@@blockquote-border-thickness": 4,
"minimal-style@@minimal-code-scroll": true,
"minimal-style@@h1-l": true,
"minimal-style@@h2-l": true,
"minimal-style@@image-muted": 1,
"minimal-style@@active-line-on": true,
"minimal-style@@minimal-strike-lists": true,
"minimal-style@@metadata-heading-off": true,
"minimal-style@@metadata-icons-off": true,
"minimal-style@@hide-help": true,
"minimal-style@@row-hover": true
} |
I've set it to private since it contains personal details (encrypted with theme/options.nix{
user ? throw "user argument is required",
home ? throw "home argument is required",
}:
{
config,
inputs,
lib,
pkgs,
...
}:
let
cfg = config.theme;
init = pkgs.writeShellApplication {
name = "theme-init";
runtimeInputs = with pkgs; [
matugen
imagemagick
];
text = ''
mkdir -p "${cfg.configDir}"
[[ ! -f "${cfg.configDir}"/wallpaper ]] && magick -size 1x1 xc:"#000000" png:"${cfg.configDir}"/wallpaper
[[ ! -f "${cfg.configDir}"/mode ]] && echo "dark" > "${cfg.configDir}"/mode
[[ ! -f "${cfg.configDir}"/flavor ]] && echo "tonal-spot" > "${cfg.configDir}"/flavor
[[ ! -f "${cfg.configDir}"/contrast ]] && echo "0" > "${cfg.configDir}"/contrast
matugen image "${cfg.configDir}"/wallpaper \
--mode "$(<"${cfg.configDir}/mode")" \
--type "scheme-$(<"${cfg.configDir}/flavor")" \
--contrast "$(<"${cfg.configDir}/contrast")"
${cfg.initExtraConfig}
wait
'';
};
reload = pkgs.writeShellApplication {
name = "theme-reload";
text = ''
${cfg.reloadExtraConfig}
wait
'';
};
theme = pkgs.writeShellApplication {
name = "theme";
runtimeInputs = with pkgs; [
coreutils
imagemagick
];
runtimeEnv = {
CONFIG = cfg.configDir;
INIT = lib.meta.getExe init;
RELOAD = lib.meta.getExe reload;
};
text = builtins.readFile ./theme.sh;
};
in
{
# https://github.com/Theaninova/TheaninovOS/blob/master/modules/home-manager/theme/md3-evo.nix
options.theme =
with lib;
with types;
{
enable = mkEnableOption "theme";
configDir = mkOption {
type = str;
default = "${home}/.config/theme";
description = "The path to the theme config directory.";
};
pkg = mkOption {
type = package;
default = theme;
readOnly = true;
description = "The package containing the `theme` script";
};
initExtraConfig = mkOption {
type = lines;
default = "";
description = "Extra configuration lines to add to the theme initialization script.";
};
reloadExtraConfig = mkOption {
type = lines;
default = "";
description = "Extra configuration lines to add to the theme reload script.";
};
template = mkOption {
type = attrsOf (
submodule (
{ name, config, ... }:
{
options = {
source = mkOption {
type = path;
description = "Path of the source file or directory.";
default = null;
};
target = mkOption {
type = str;
defaultText = literalExpression "name";
description = "Path to target relative to the user's {env}`HOME`.";
};
};
config.target = mkDefault name;
}
)
);
default = { };
description = "Templates to fill with theme colors.";
};
opacity = mkOption {
type = numbers.between 0 1;
default = 1;
description = "The opacity of apps.";
};
radius = mkOption {
type = ints.unsigned;
default = 0;
description = "The radius of corners.";
};
padding = mkOption {
type = ints.unsigned;
default = 0;
description = "The padding of windows.";
};
blur = mkOption {
type = ints.unsigned;
default = 0;
description = "The blur amount of windows.";
};
color = {
semantic = {
blend = mkOption {
type = bool;
default = true;
description = "Blend the colors.";
};
danger = mkOption {
type = str;
default = "#ff0000";
description = "The color of danger.";
};
warning = mkOption {
type = str;
default = "#ffff00";
description = "The color of warning.";
};
success = mkOption {
type = str;
default = "#00ff00";
description = "The color of success.";
};
info = mkOption {
type = str;
default = "#0000ff";
description = "The color of info.";
};
};
syntax = {
blend = mkOption {
type = bool;
default = true;
description = "Blend the colors.";
};
keywords = mkOption {
type = str;
default = "#ff8000";
description = "The color of keywords.";
};
functions = mkOption {
type = str;
default = "#0000ff";
description = "The color of functions.";
};
properties = mkOption {
type = str;
default = "#ff00ff";
description = "The color of properties.";
};
constants = mkOption {
type = str;
default = "#ff00ff";
description = "The color of constants.";
};
strings = mkOption {
type = str;
default = "#00ff00";
description = "The color of variables.";
};
numbers = mkOption {
type = str;
default = "#00ffff";
description = "The color of numbers.";
};
structures = mkOption {
type = str;
default = "#ffff00";
description = "The color of structures.";
};
types = mkOption {
type = str;
default = "#00ffff";
description = "The color of types.";
};
};
basic = {
blend = mkOption {
type = bool;
default = true;
description = "Blend the colors.";
};
red = mkOption {
type = str;
default = "#ff0000";
description = "The color of red.";
};
orange = mkOption {
type = str;
default = "#ff8000";
description = "The color of orange.";
};
yellow = mkOption {
type = str;
default = "#ffff00";
description = "The color of yellow.";
};
green = mkOption {
type = str;
default = "#00ff00";
description = "The color of green.";
};
cyan = mkOption {
type = str;
default = "#00ffff";
description = "The color of cyan.";
};
blue = mkOption {
type = str;
default = "#0000ff";
description = "The color of blue.";
};
magenta = mkOption {
type = str;
default = "#ff00ff";
description = "The color of magenta.";
};
pink = mkOption {
type = str;
default = "#ffc0cb";
description = "The color of pink.";
};
};
};
font = {
sansSerif = {
names = mkOption {
type = listOf str;
default = [ "Roboto" ];
description = "The sans serif font families.";
};
packages = mkOption {
type = listOf package;
default = with pkgs; [ roboto ];
description = "The sans serif font packages.";
};
};
serif = {
names = mkOption {
type = listOf str;
default = [ "Roboto Serif" ];
description = "The serif font families.";
};
packages = mkOption {
type = listOf package;
default = with pkgs; [ roboto-serif ];
description = "The serif font packages.";
};
};
monospace = {
names = mkOption {
type = listOf str;
default = [ "JetBrainsMono Nerd Font" ];
description = "The monospace font families.";
};
packages = mkOption {
type = listOf package;
default = with pkgs; [ nerd-fonts.jetbrains-mono ];
description = "The monospace font packages.";
};
};
emoji = {
names = mkOption {
type = listOf str;
default = [
"Noto Emoji"
"Font Awesome"
];
description = "The emoji font families.";
};
packages = mkOption {
type = listOf package;
default = with pkgs; [
noto-fonts-color-emoji
font-awesome
];
description = "The emoji font packages.";
};
};
size = mkOption {
type = ints.positive;
default = 12;
description = "The font size.";
};
};
icon = {
names = mkOption {
type = listOf str;
default = [ "Adwaita" ];
description = "The icon theme names.";
};
packages = mkOption {
type = listOf package;
default = with pkgs; [
adwaita-icon-theme
nixos-icons
];
description = "The icon theme packages.";
};
};
cursor = {
names = mkOption {
type = listOf str;
default = [ "Adwaita" ];
description = "The cursor names.";
};
packages = mkOption {
type = listOf package;
default = with pkgs; [ adwaita-icon-theme ];
description = "The cursor theme packages.";
};
size = mkOption {
type = ints.positive;
default = 24;
description = "The cursor size.";
};
};
};
config = lib.mkIf cfg.enable {
home = {
activation.themeInit = inputs.home-manager.lib.hm.dag.entryAfter [
"writeBoundary"
"dconfSettings"
] "run ${lib.meta.getExe init}";
packages =
with pkgs;
[
matugen
theme
]
++ cfg.font.sansSerif.packages
++ cfg.font.serif.packages
++ cfg.font.monospace.packages
++ cfg.font.emoji.packages
++ cfg.icon.packages
++ cfg.cursor.packages;
pointerCursor = {
name = builtins.head cfg.cursor.names;
package = builtins.head cfg.cursor.packages;
inherit (cfg.cursor) size;
};
};
fonts.fontconfig = {
enable = true;
defaultFonts = {
sansSerif = cfg.font.sansSerif.names;
serif = cfg.font.serif.names;
monospace = cfg.font.monospace.names;
emoji = cfg.font.emoji.names;
};
};
xdg.configFile."matugen/config.toml".source = (
(pkgs.formats.toml { }).generate "config.toml" {
config = {
custom_colors =
let
mkColor = category: color: {
color = cfg.color.${category}.${color};
blend = cfg.color.${category}.blend;
};
in
{
danger = mkColor "semantic" "danger";
warning = mkColor "semantic" "warning";
success = mkColor "semantic" "success";
info = mkColor "semantic" "info";
red = mkColor "basic" "red";
orange = mkColor "basic" "orange";
yellow = mkColor "basic" "yellow";
green = mkColor "basic" "green";
cyan = mkColor "basic" "cyan";
blue = mkColor "basic" "blue";
magenta = mkColor "basic" "magenta";
pink = mkColor "basic" "pink";
keywords = mkColor "syntax" "keywords";
functions = mkColor "syntax" "functions";
properties = mkColor "syntax" "properties";
constants = mkColor "syntax" "constants";
strings = mkColor "syntax" "strings";
numbers = mkColor "syntax" "numbers";
structures = mkColor "syntax" "structures";
types = mkColor "syntax" "types";
};
custom_keywords =
let
zeroPad = hex: if builtins.stringLength hex == 1 then "0${hex}" else hex;
percentageToHex = percentage: zeroPad (lib.trivial.toHexString (builtins.floor (percentage * 255)));
in
{
radius = builtins.toString cfg.radius;
padding = builtins.toString cfg.padding;
padding_double = builtins.toString (cfg.padding * 2);
blur = builtins.toString cfg.blur;
opacity = builtins.toString cfg.opacity;
opacity_hex = builtins.toString (percentageToHex cfg.opacity);
opacity_shadow = builtins.toString (cfg.opacity * 0.75);
opacity_shadow_hex = builtins.toString (percentageToHex (cfg.opacity * 0.75));
font_size = builtins.toString cfg.font.size;
font_sans_serif = builtins.head cfg.font.sansSerif.names;
font_sans_serif_all = builtins.concatStringsSep ", " cfg.font.sansSerif.names;
font_serif = builtins.head cfg.font.serif.names;
font_serif_all = builtins.concatStringsSep ", " cfg.font.serif.names;
font_monospace = builtins.head cfg.font.monospace.names;
font_monospace_all = builtins.concatStringsSep ", " cfg.font.monospace.names;
font_emoji = builtins.head cfg.font.emoji.names;
font_emoji_all = builtins.concatStringsSep ", " cfg.font.emoji.names;
};
};
templates = builtins.mapAttrs (name: template: {
input_path = template.source;
output_path = "${home}/${template.target}";
}) cfg.template;
}
);
programs.zsh.initContent = builtins.readFile ./theme.completion.zsh;
};
}theme/theme.sh# shellcheck shell=bash
wallpaper=""
color=""
mode=""
flavor=""
contrast=""
set_wallpaper() {
if [[ -f "$1" ]]; then
wallpaper="$(realpath "$1")"
else
echo "Invalid wallpaper path: $1"
exit 1
fi
}
set_color() {
local re='^#([A-Fa-f0-9]{6})$'
if [[ "$1" =~ $re ]]; then
color="$1"
else
echo "Invalid color format: $1"
exit 1
fi
}
set_flavor() {
case "$1" in
content|expressive|fidelity|fruit-salad|monochrome|neutral|rainbow|tonal-spot)
flavor="$1"
;;
*)
echo "Invalid flavor: $1"
exit 1
;;
esac
}
set_contrast() {
if [[ "$1" =~ ^-?[0-1](\.[0-9]+)?$ ]]; then
contrast="$1"
else
echo "Invalid contrast value: $1"
exit 1
fi
}
toggle_mode() {
if [[ "$(cat "$CONFIG"/mode)" = "light" ]]; then
mode="dark"
else
mode="light"
fi
}
usage() {
echo "Usage: $0 [-m {light|dark|toggle}] [-w <file>] [-h <hexcolor>] [-f <flavor>] [-c <contrast>]"
echo " Only one of -w (wallpaper) or -h (hex color) can be used."
echo " Valid flavors: content, expressive, fidelity, fruit-salad, monochrome, neutral, rainbow, tonal-spot"
echo " Contrast must be a number between -1 and 1"
exit 1
}
finish() {
[[ -n "$wallpaper" ]] && rm -f "$CONFIG"/wallpaper && ln -sf "$wallpaper" "$CONFIG"/wallpaper
[[ -n "$color" ]] && rm -f "$CONFIG"/wallpaper && magick -size 1x1 xc:"$color" png:"$CONFIG"/wallpaper
[[ -n "$mode" ]] && echo "$mode" > "$CONFIG"/mode
[[ -n "$flavor" ]] && echo "$flavor" > "$CONFIG"/flavor
[[ -n "$contrast" ]] && echo "$contrast" > "$CONFIG"/contrast
"$INIT" > /dev/null
"$RELOAD" > /dev/null
}
# Parse arguments
while getopts "m:w:h:f:c:" opt; do
case "$opt" in
m)
case "$OPTARG" in
light|dark) mode="$OPTARG" ;;
toggle) toggle_mode ;;
*) usage ;;
esac
;;
w)
[[ -n "$color" ]] && usage
set_wallpaper "$OPTARG"
;;
h)
[[ -n "$wallpaper" ]] && usage
set_color "$OPTARG"
;;
f)
set_flavor "$OPTARG"
;;
c)
set_contrast "$OPTARG"
;;
*)
usage
;;
esac
done
finishobsidian/default.nix...
{
...
}:
let
hmConfig = config.home-manager.users.${user};
in
{
environment.persistence."/persist/cache"."${home}/.config/obsidian" = { };
home-manager.users.${user} = {
programs.obsidian = {
enable = true;
...
};
theme.template = lib.attrsets.mapAttrs' (
_: vault:
lib.attrsets.nameValuePair "${vault.target}/.obsidian/plugins/obsidian-style-settings/data.json" {
source = ./theme.json;
}
) hmConfig.programs.obsidian.vaults;
...
};
} |
Motivation
Currently, Obsidian lacks first-class Nix integration (#6321). Users have to manually install the app and manage settings, plugins, and themes outside of Nix. This module aims to make Obsidian declarative by handling vaults, themes, and plugin management via Home-Manager.
Overview
This module provides:
extraFiles- Symlinking arbitrary files in a vault.Example Configuration
Open Questions
defaultSettings?optionsandsettings, or is something else more appropriate, e.g.config?Checklist
Change is backwards compatible.
Code formatted with
./format.Code tested through
nix-shell --pure tests -A run.allor
nix build --reference-lock-file flake.lock ./tests#test-allusing Flakes.Test cases updated/added. See example.
Commit messages are formatted
Added myself as module maintainer. See example.
Maintainer CC
@rycee