From db77b2099c45bf0f5fec5516bb0e470cf8a8ed23 Mon Sep 17 00:00:00 2001 From: 6iovan Date: Thu, 16 Oct 2025 07:41:39 +0800 Subject: [PATCH] feat(hm-module): pins declaration (#132) --- hm-module.nix | 196 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 3 deletions(-) diff --git a/hm-module.nix b/hm-module.nix index 1796156..c23e986 100644 --- a/hm-module.nix +++ b/hm-module.nix @@ -196,6 +196,81 @@ in { ); default = {}; }; + pinsForce = mkOption { + type = bool; + description = "Whether to delete existing pins not declared in the configuration."; + default = false; + }; + pins = mkOption { + type = attrsOf ( + submodule ( + { name, ... }: { + options = { + title = mkOption { + type = str; + description = "title of the pin."; + default = name; + }; + id = mkOption { + type = str; + description = "REQUIRED. Unique Version 4 UUID for pin."; + }; + url = mkOption { + type = nullOr str; + default = null; + description = "Optional URL text field"; + }; + container = mkOption { + type = nullOr ints.unsigned; + default = null; + description = "Container ID to be used in pin"; + }; + workspace = mkOption { + type = nullOr str; + default = null; + description = "Workspace ID to be used in pin"; + }; + position = mkOption { + type = ints.unsigned; + default = 1000; + description = "Position of the pin."; + }; + isEssential = mkOption { + type = bool; + default = false; + description = "Required boolean flag for essential items, defaults to false"; + }; + isGroup = mkOption { + type = bool; + default = false; + description = "Required boolean flag for group items, defaults to false"; + }; + editedTitle = mkOption { + type = bool; + default = false; + description = "Required boolean flag for edited title, defaults to false"; + }; + isFolderCollapsed = mkOption { + type = bool; + default = false; + description = "Required boolean flag for folder collapse state, defaults to false"; + }; + folderIcon = mkOption { + type = nullOr str; + description = "Emoji or icon URI to be used as pin folder icon."; + default = null; + }; + folderParentId = mkOption { + type = nullOr str; + default = null; + description = "Optional folder parent UUID text field"; + }; + }; + } + ) + ); + default = { }; + }; }; } ) @@ -390,6 +465,119 @@ in { else "" }${concatMapAttrsStringSep " AND " (_: s: "NOT uuid = '{${s.id}}'") profile.spaces}" || exit 1 ''; + + insertPins = '' + #Reference https://github.com/zen-browser/desktop/blob/28bf0458e43e2bb741cd67834d2b50ce2b5587c6/src/zen/tabs/ZenPinnedTabsStorage.mjs#L12-L26 + # Create the pins table if it doesn't exist + ${sqlite3} "${placesFile}" "${ + concatStringsSep " " [ + "CREATE TABLE IF NOT EXISTS zen_pins (" + "id INTEGER PRIMARY KEY," + "uuid TEXT UNIQUE NOT NULL," + "title TEXT NOT NULL," + "url TEXT," + "container_id INTEGER," + "workspace_uuid TEXT," + "position INTEGER NOT NULL DEFAULT 0," + "is_essential BOOLEAN NOT NULL DEFAULT 0," + "is_group BOOLEAN NOT NULL DEFAULT 0," + "created_at INTEGER NOT NULL," + "updated_at INTEGER NOT NULL" + ");" + ] + }" || exit 1 + + columns=($(${sqlite3} "${placesFile}" "SELECT name FROM pragma_table_info('zen_pins');")) + + if [[ ! "''${columns[@]}" =~ "edited_title" ]]; then + ${sqlite3} "${placesFile}" "ALTER TABLE zen_pins ADD COLUMN edited_title BOOLEAN NOT NULL DEFAULT 0;" || exit 1 + fi + if [[ ! "''${columns[@]}" =~ "is_folder_collapsed" ]]; then + ${sqlite3} "${placesFile}" "ALTER TABLE zen_pins ADD COLUMN is_folder_collapsed BOOLEAN NOT NULL DEFAULT 0;" || exit 1 + fi + if [[ ! "''${columns[@]}" =~ "folder_icon" ]]; then + ${sqlite3} "${placesFile}" "ALTER TABLE zen_pins ADD COLUMN folder_icon TEXT DEFAULT NULL;" || exit 1 + fi + if [[ ! "''${columns[@]}" =~ "folder_parent_uuid" ]]; then + ${sqlite3} "${placesFile}" "ALTER TABLE zen_pins ADD COLUMN folder_parent_uuid TEXT DEFAULT NULL;" || exit 1 + fi + + # Reference https://github.com/zen-browser/desktop/blob/28bf0458e43e2bb741cd67834d2b50ce2b5587c6/src/zen/tabs/ZenPinnedTabsStorage.mjs#L103-L112 + # Insert or replace the pin + ${sqlite3} "${placesFile}" <<-'SQL' || exit 1 + ${ + (concatStringsSep " " [ + "INSERT OR REPLACE INTO zen_pins (" + "uuid," + "title," + "url," + "container_id," + "workspace_uuid," + "position," + "is_essential," + "is_group," + "folder_parent_uuid," + "edited_title," + "created_at," + "updated_at," + "is_folder_collapsed," + "folder_icon" + ") VALUES " + ]) + + + (pipe profile.pins [ + (mapAttrsToList ( + _: p: [ + "'{${p.id}}'" + "'${p.title}'" + ( + if isNull p.url + then "NULL" + else "'${p.url}'" + ) + ( + if isNull p.container + then "NULL" + else toString p.container + ) + ( + if isNull p.workspace + then "NULL" + else "'{${p.workspace}}'" + ) + (toString p.position) + (if p.isEssential then "1" else "0") + (if p.isGroup then "1" else "0") + ( + if isNull p.folderParentId + then "NULL" + else "'{${p.folderParentId}}'" + ) + (if p.editedTitle then "1" else "0") + "COALESCE((SELECT created_at FROM zen_pins WHERE uuid = '{${p.id}}'), strftime('%s', 'now'))" + "strftime('%s', 'now')" + (if p.isFolderCollapsed then "1" else "0") + ( + if isNull p.folderIcon + then "NULL" + else "'${p.folderIcon}'" + ) + ] + )) + (map (row: concatStringsSep "," row)) + (concatMapStringsSep "," (row: "(${row})")) + ]) + } + SQL + ''; + + deletePins = '' + ${sqlite3} "${placesFile}" "DELETE FROM zen_pins ${ + if profile.pins != {} + then "WHERE " + else "" + }${concatMapAttrsStringSep " AND " (_: p: "NOT uuid = '{${p.id}}'") profile.pins}" || exit 1 + ''; in nameValuePair scriptFile { source = getExe ( @@ -397,12 +585,14 @@ in { # This file is generated by Zen browser Home Manager module, please to not change it since it # will be overridden and executed on every rebuild of the home environment. - function update_spaces() { + function update_places() { ${optionalString (profile.spaces != {}) insertSpaces} ${optionalString (profile.spacesForce) deleteSpaces} + ${optionalString (profile.pins != {}) insertPins} + ${optionalString (profile.pinsForce) deletePins} } - error="$(update_spaces 2>&1 1>/dev/null)" + error="$(update_places 2>&1 1>/dev/null)" if [[ "$?" -ne 0 ]]; then if [[ "$error" == *"database is locked"* ]]; then echo "$error" @@ -432,6 +622,6 @@ in { executable = true; force = true; } - ) (filterAttrs (_: profile: profile.spaces != {} || profile.spacesForce) cfg.profiles)); + ) (filterAttrs (_: profile: profile.spaces != {} || profile.spacesForce || profile.pins != { } || profile.pinsForce) cfg.profiles)); }; }