Compare commits

...

4 Commits

Author SHA1 Message Date
willmiao
6850b35770 docs: auto-update supporters list in README 2026-06-12 15:38:33 +00:00
Will Miao
237a015cde chore(release): bump version to v1.1.0 2026-06-12 23:38:16 +08:00
Will Miao
1ae2778baa feat(sidebar): add per-page hide toggle with more options dropdown
- Add ``` button in sidebar header with dropdown menu
- Add "Hide sidebar on this page" option with per-page localStorage state
- Show edge indicator (14px chevron) on left when hidden per-page
- Show brief toast notification when hiding
- Fix container margin not resetting when sidebar is per-page hidden
- Add i18n translations for all 10 locales
2026-06-12 18:27:54 +08:00
Will Miao
84fcdb5f20 fix(recipe): compute folder field on save to prevent reimported recipes disappearing from subfolder grid 2026-06-12 16:49:57 +08:00
17 changed files with 535 additions and 147 deletions

File diff suppressed because one or more lines are too long

View File

@@ -6,22 +6,29 @@
"Scott R"
],
"allSupporters": [
"Brennok",
"Insomnia Art Designs",
"2018cfh",
"megakirbs",
"Brennok",
"Arlecchino Shion",
"Rob Williams",
"W+K+White",
"wackop",
"Phil",
"Carl G.",
"Arlecchino Shion",
"Charles Blakemore",
"Rob Williams",
"stone9k",
"itismyelement",
"$MetaSamsara",
"Gingko Biloba",
"Kiba",
"onesecondinosaur",
"Christian Byrne",
"DM",
"Sen314",
"Estragon",
"Rosenthal",
"ClockDaemon",
"Francisco Tatis",
"Tobi_Swagg",
"Andrew Wilson",
@@ -30,32 +37,38 @@
"JongWon Han",
"VantAI",
"runte3221",
"Illrigger",
"Tom Corrigan",
"FreelancerZ",
"Echo",
"Robert Stacey",
"Edgar Tejeda",
"Fraser Cross",
"Liam MacDougal",
"Polymorphic Indeterminate",
"Sterilized",
"JORGE+LUIZ+HUSSNI+MESSIAS",
"Marc Whiffen",
"Skalabananen",
"Birdy",
"Kiba",
"Mozzel",
"Gingko Biloba",
"Reno Lam",
"JSST",
"sig",
"Christian Byrne",
"DM",
"Sen314",
"Estragon",
"J\\B/ 8r0wns0n",
"Snaggwort",
"Takkan",
"Matt+J",
"ClockDaemon",
"Baekdoosixt",
"Jonathan Ross",
"KD",
"Omnidex",
"Nazono_hito",
"daniel dove",
"Tyler Trebuchon",
"Release Cabrakan",
"JW Sin",
"Alex",
"SG",
"carozzz",
"James Dooley",
@@ -70,77 +83,71 @@
"RedrockVP",
"Wolffen",
"James Todd",
"Wicked Choices by ASLPro3D",
"Steven Pfeiffer",
"Tim",
"レプサイ",
"Timmy",
"Johnny",
"Tak",
"Lisster",
"Michael Wong",
"Illrigger",
"Big Red",
"whudunit",
"Tom Corrigan",
"dl0901dm",
"JackieWang",
"fnkylove",
"Yushio",
"Vik71it",
"Echo",
"Bishoujoker",
"Lilleman",
"Robert Stacey",
"PM",
"Todd Keck",
"Briton Heilbrun",
"Jorge Hussni",
"Sterilized",
"wildnut",
"Aleksander Wujczyk",
"AM Kuro",
"BadassArabianMofo",
"Pascal Dahle",
"quarz",
"Greg",
"JSST",
"lmsupporter",
"andrew.tappan",
"zounic",
"wfpearl",
"Baekdoosixt",
"Jonathan Ross",
"Jack B Nimble",
"Nazono_hito",
"Melville Parrish",
"daniel dove",
"Lustre",
"JW Sin",
"JaxMax",
"contrite831",
"Alex",
"bh",
"Marlon Daniels",
"Starkselle",
"Aaron Bleuer",
"LacesOut!",
"greebles",
"Some Guy Named Barry",
"M Postkasse",
"Gooohokrbe",
"Wicked Choices by ASLPro3D",
"OldBones",
"Jacob Hoehler",
"FinalyFree",
"Matt Wenzel",
"Weasyl",
"Lex Song",
"Cory Paza",
"Tak",
"Gonzalo Andre Allendes Lopez",
"Zach Gonser",
"Big Red",
"Jimmy Ledbetter",
"Luc Job",
"dl0901dm",
"Philip Hempel",
"corde",
"Nick Walker",
"Bishoujoker",
"dan",
"aai",
"Tori",
"wildnut",
"otaku fra",
"jean jahren",
"Aleksander Wujczyk",
"AM Kuro",
"MiraiKuriyamaSy",
"Ran C",
"ViperC",
"Penfore",
@@ -149,17 +156,22 @@
"Karl P.",
"Akira_HentAI",
"Gordon Cole",
"Adam Taylor",
"AbstractAss",
"andrew.tappan",
"Weird_With_A_Beard",
"N/A",
"The Spawn",
"graysock",
"Pozadine1",
"Qarob",
"AIGooner",
"Luc",
"Greenmoustache",
"Jackthemind",
"fancypants",
"Eldithor",
"Joboshy",
"Digital",
"JaxMax",
"takyamtom",
"Bohemian Corporal",
"Dan",
@@ -170,42 +182,37 @@
"carey6409",
"Olive",
"太郎 ゲーム",
"Some Guy Named Barry",
"Roslynd",
"jinxedx",
"Cosmosis",
"AELOX",
"Dankin",
"Nicfit23",
"FloPro4Sho",
"Cristian Vazquez",
"wamekukyouzin",
"drum matthieu",
"Dogmaster",
"Matt Wenzel",
"Frank Nitty",
"Magic Noob",
"Christopher Michel",
"Serge Bekenkamp",
"DougPeterson",
"LeoZero",
"Antonio Pontes",
"ApathyJones",
"Julian V",
"Steven Owens",
"nahinahi9",
"Kevin John Duck",
"Dustin Chen",
"dan",
"Blackfish95",
"Mouthlessman",
"Paul Kroll",
"otaku fra",
"MiraiKuriyamaSy",
"Bas Imagineer",
"yuxz69",
"Adam Taylor",
"Weird_With_A_Beard",
"esthe",
"Pozadine1",
"Qarob",
"AIGooner",
"Luc",
"decoy",
"ProtonPrince",
"DiffDuck",
"elu3199",
@@ -217,46 +224,50 @@
"wundershark",
"mr_dinosaur",
"Tyrswood",
"Ray Wing",
"Ranzitho",
"Gus",
"MJG",
"linnfrey",
"IamAyam",
"skaterb949",
"Josef Lanzl",
"Nerezza",
"confiscated Zyra",
"Error_Rule34_Not_found",
"aezin",
"jcay015",
"Gerald Welly",
"Roslynd",
"Erik Lopez",
"Mateo Curić",
"Tee Gee",
"Geolog",
"tarek helmi",
"Neco28",
"Eris3D",
"Max Marklund",
"David Ortega",
"Cristian Vazquez",
"Magic Noob",
"Pronredn",
"DougPeterson",
"a _",
"Jeff",
"Bruce",
"lh qwe",
"Kevin John Duck",
"James Coleman",
"conner",
"Kevin Christopher",
"Chad Idk",
"dd",
"Princess Bright Eyes",
"Dušan Ryban",
"Felipe dos Santos",
"sjon kreutz",
"John Statham",
"Douglas Gaspar",
"Metryman55",
"AlexDuKaNa",
"George",
"dw",
"decoy",
"Ray Wing",
"Ranzitho",
"Gus",
"地獄の禄",
"MJG",
"David LaVallee",
"ae",
"Tr4shP4nda",
@@ -273,19 +284,20 @@
"몽타주",
"Kland",
"Hailshem",
"kudari",
"Naomi Hale Danchi",
"epicgamer0020690",
"Richard",
"奚明 刘",
"Andrew",
"Brian M",
"Nerezza",
"Robert Wegemund",
"sanborondon",
"준희 김",
"Taylor Funk",
"aezin",
"Thought2Form",
"jcay015",
"Kevin Picco",
"Erik Lopez",
"Mateo Curić",
"Eris3D",
"Sadlip",
"Tomohiro Baba",
"m",
"Noora",
@@ -294,32 +306,30 @@
"Mattssn",
"Mikko Hemilä",
"Jamie Ogletree",
"a _",
"James Coleman",
"Michael Taylor",
"Martial",
"Emil Andersson",
"Ouro Boros",
"Chad Idk",
"Atilla Berke Pekduyar",
"Steam Steam",
"CryptoTraderJK",
"Decx _",
"Yuji Kaneko",
"Davaitamin",
"Rops Alot",
"tedcor",
"Sam",
"Fotek Design",
"sjon kreutz",
"Ace Ventura",
"LarsesFPC",
"MadSpin",
"inbijiburu",
"Nick “Loadstone” D",
"momokai",
"starbugx",
"kudari",
"Naomi Hale Danchi",
"dc7431",
"ken",
"epicgamer0020690",
"Crocket",
"Joshua Porrata",
"keemun",
"SuBu",
@@ -339,22 +349,24 @@
"KitKatM",
"socrasteeze",
"OrganicArtifact",
"MudkipMedkitz",
"deanbrian",
"Alex Wortman",
"Cody",
"emadsultan",
"Vir",
"gzmzmvp",
"Richard",
"Andrew",
"Robert Wegemund",
"Littlehuggy",
"Gregory Kozhemiak",
"Draven T",
"mrjuan",
"Brian Buie",
"Sadlip",
"Eric Whitney",
"Joey Callahan",
"Aquatic Coffee",
"Ivan Tadic",
"Mike Simone",
"John J Linehan",
"ethanfel",
"Elliot E",
"Morgandel",
@@ -366,34 +378,30 @@
"Sloan Steddy",
"Temikus",
"Artokun",
"Michael Taylor",
"hexxish",
"Derek Baker",
"Anthony Faxlandez",
"battu",
"Michael Anthony Scott",
"Atilla Berke Pekduyar",
"Nathan",
"Decx _",
"NICHOLAS BAXLEY",
"Pat Hen",
"Xeeosat",
"Ed Wang",
"Jordan Shaw",
"g unit",
"Srdb",
"四糸凜音",
"Nihongasuki",
"LarsesFPC",
"JC",
"Prompt Pirate",
"uwutismxd",
"FrxzenSnxw",
"zenobeus",
"Crocket",
"Jackthemind",
"ryoma",
"Stryker",
"ResidentDeviant",
"MudkipMedkitz",
"deanbrian",
"Alex Wortman",
"Cody",
"Ginnie",
"Raku",
"smart.edge5178",
"InformedViewz",
@@ -415,6 +423,15 @@
"SpringBootisTrash",
"carsten",
"ikok",
"DarkRoast",
"letzte",
"Nasty+Hobbit",
"Sora+Yori",
"lrdchs2",
"Duk3+Rand0m",
"Nathen+Choi",
"T",
"cocona",
"ElitaSSJ4",
"David Schenck",
"Wolfe7D1",
@@ -426,7 +443,6 @@
"Goldwaters",
"Kauffy",
"Zude",
"John J Linehan",
"Kyler",
"Edward Kennedy",
"Justin Blaylock",
@@ -435,17 +451,14 @@
"Vane Holzer",
"psytrax",
"Cyrus Fett",
"hexxish",
"Xenon Xue",
"notedfakes",
"Billy Gladky",
"NICHOLAS BAXLEY",
"Michael Scott",
"Probis",
"Ed Wang",
"Wes Sims",
"ItsGeneralButtNaked",
"Donor4115",
"g unit",
"Distortik",
"Filippo Ferrari",
"Youguang",
@@ -460,9 +473,14 @@
"Mitchell Robson",
"Whitepinetrader",
"POPPIN",
"Ginnie",
"emadsultan",
"nanana",
"ChaChanoKo",
"ghoulars",
"null",
"Beau",
"redcarrot",
"powerbot99",
"Fthehappy",
"g",
"J",
"Alan+Cano",
@@ -474,15 +492,6 @@
"quantenmecha",
"Jason+Nash",
"BillyBoy84",
"DarkRoast",
"letzte",
"Nasty+Hobbit",
"Sora+Yori",
"lrdchs2",
"Duk3+Rand0m",
"Nathen+Choi",
"T",
"cocona",
"Buecyb99",
"Welkor",
"John Martin",
@@ -491,6 +500,8 @@
"moranqianlong",
"Kalli Core",
"Time Valentine",
"Christian Schäfer",
"りん あめ",
"Михал Михалыч",
"Matt",
"Frogmilk",
@@ -501,21 +512,26 @@
"Anonym dkjglfleeoeldldldlkf",
"Ezokewn",
"SendingRavens",
"Xenon Xue",
"JackJohnnyJim",
"TenaciousD",
"Dmitry Ryzhov",
"Khánh Đặng",
"Edward Ten Eyck",
"Michael Docherty",
"Jimmy Borup",
"Paul Hartsuyker",
"Henrique Faiolli",
"elitassj",
"Solixer",
"Pete Pain",
"Jacob Winter",
"Ryan Presley Ng",
"jinksta187",
"RHopkirk",
"Andrew Wilkinson",
"Manu Thetug",
"Karlanx",
"Lyavph",
"Maxim",
"David",
"Meilo",
"operationancut",
@@ -537,6 +553,17 @@
"Scott",
"Muratoraccio",
"D",
"2turbo",
"Somebody",
"Balut+Omelette",
"Dmitry+Viznesenskiy",
"tanjin90",
"sternenkrieger",
"eriick",
"Patrick+Bryan",
"Pascalou",
"lighthawke",
"Lev+Lanevskiy",
"low9",
"Winged",
"YassineKhaled",
@@ -552,13 +579,6 @@
"Alex",
"Jacky+Ho",
"Karru",
"ghoulars",
"ChaChanoKo",
"null",
"Beau",
"redcarrot",
"powerbot99",
"Fthehappy",
"generic404",
"abattoirblues",
"zounik",
@@ -568,9 +588,10 @@
"Bob Barker",
"edk",
"Tú Nguyễn Lý Hoàng",
"shira1011",
"Ben D",
"G",
"Ronan Delevacq",
"Christian Schäfer",
"りん あめ",
"ja s",
"Doug Mason",
"Jeremy Townsend",
@@ -580,38 +601,41 @@
"Sean voets",
"Owen Gwosdz",
"Jarrid Lee",
"Poophead27 Blyat",
"Kor",
"Joseph Hanson",
"John Rednoulf",
"Spire",
"Boba Smith",
"Devil Lude",
"David Murcko",
"MR.Bear",
"Jack Dole",
"somethingtosay8",
"ivistorm",
"max blo",
"Sauv",
"Steven",
"CptNeo",
"TenaciousD",
"Dmitry Ryzhov",
"Khánh Đặng",
"Maso",
"Ted Cart",
"Sage Himeros",
"Eric Ketchum",
"Kevin Wallace",
"Jimmy Borup",
"David Spearing",
"ChicRic",
"Tigon",
"BastardSama",
"mercur",
"Pete Pain",
"RHopkirk",
"Tania Nayelli Fernandez",
"Draconach",
"Yavizu3d",
"Maxim",
"Yves Poezevara",
"Teriak47",
"Just me",
"Raf Stahelin",
"Вячеслав Маринин",
"Dkommander22",
"Cola Matthew",
"OniNoKen",
"Iain Wisely",
@@ -655,6 +679,17 @@
"SelfishMedic",
"adderleighn",
"EnragedAntelope",
"Monix",
"Trolinka",
"IshouI;_;",
"PredragR",
"Clauzmak",
"Nerick",
"JoL",
"Gold_miner_ego",
"SundayRage",
"YoruHime",
"matter",
"SRCRCOSS",
"imer",
"Akkas+Haque",
@@ -675,18 +710,8 @@
"Sildoren",
"Darvidous",
"Seon+Song",
"2turbo",
"balut+omelette",
"Nebuleux",
"Dmitry+Viznesenskiy",
"Tanjin90",
"Somebody",
"sternenkrieger",
"eriick",
"Join+Chun",
"Pascalou",
"lighthawke",
"Terraformer",
"GDS+DEV",
"4rt+r3d",
"you+halo9",
@@ -712,17 +737,16 @@
"_ G3n",
"Donovan Jenkins",
"Hans Meier",
"shira1011",
"sicarius",
"Michael Eid",
"Wolf and Fox Legends",
"beersandbacon",
"Neko Desco",
"Bob barker",
"Ben D",
"Ninja Tom",
"G",
"karim ben brik",
"Vinarus",
"Josh Snyder",
"Michael Zhu",
"Nemisu",
"Seraphy",
@@ -732,41 +756,42 @@
"jumpd",
"John C",
"Rim",
"Room Light",
"Jairus Knudsen",
"Poophead27 Blyat",
"Xan Dionysus",
"Patryk Serious",
"Nathan lee",
"Lyle Liston",
"lylepaul",
"Middo",
"Forbidden Atelier",
"Thomas Sankowski",
"Spire",
"DrB",
"AZ Party Oasis",
"Adictedtohumping",
"Snorklebort",
"Towelie",
"TheFusion",
"matt",
"dsffsdfsdfsdfsdfsdf",
"somethingtosay8",
"Jean-françois SEMA",
"3zS4QNQ4",
"Terminuz",
"Kurt",
"ivistorm",
"Matt M.",
"Ivan Imes",
"J M",
"Bouya shaka",
"Faburizu",
"Jack Lawfield",
"jimyjomson",
"Borte",
"JaeHyun Jang",
"Chase Kwon",
"Ted Cart",
"Sage Himeros",
"yyuvuvu",
"Inyoshu",
"Chad Barnes",
"Person Y",
"David Spearing",
"Nomki",
"James Ming",
"vanditking",
"kripitonga",
@@ -787,5 +812,5 @@
"Somebody",
"CK"
],
"totalCount": 784
"totalCount": 809
}

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "Stammverzeichnis",
"moreOptions": "Weitere Optionen",
"collapseAll": "Alle Ordner einklappen",
"pinSidebar": "Sidebar anheften",
"unpinSidebar": "Sidebar lösen",
"hideOnThisPage": "Seitenleiste auf dieser Seite ausblenden",
"showSidebar": "Seitenleiste anzeigen",
"sidebarHiddenNotification": "Seitenleiste auf der Seite {page} ausgeblendet",
"switchToListView": "Zur Listenansicht wechseln",
"switchToTreeView": "Zur Baumansicht wechseln",
"recursiveOn": "Unterordner einbeziehen",

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "Root",
"moreOptions": "More options",
"collapseAll": "Collapse All Folders",
"pinSidebar": "Pin Sidebar",
"unpinSidebar": "Unpin Sidebar",
"hideOnThisPage": "Hide sidebar on this page",
"showSidebar": "Show sidebar",
"sidebarHiddenNotification": "Folder sidebar hidden on {page} page",
"switchToListView": "Switch to List View",
"switchToTreeView": "Switch to Tree View",
"recursiveOn": "Include subfolders",

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "Raíz",
"moreOptions": "Más opciones",
"collapseAll": "Colapsar todas las carpetas",
"pinSidebar": "Fijar barra lateral",
"unpinSidebar": "Desfijar barra lateral",
"hideOnThisPage": "Ocultar barra lateral en esta página",
"showSidebar": "Mostrar barra lateral",
"sidebarHiddenNotification": "Barra lateral oculta en la página {page}",
"switchToListView": "Cambiar a vista de lista",
"switchToTreeView": "Cambiar a vista de árbol",
"recursiveOn": "Incluir subcarpetas",

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "Racine",
"moreOptions": "Plus d'options",
"collapseAll": "Réduire tous les dossiers",
"pinSidebar": "Épingler la barre latérale",
"unpinSidebar": "Désépingler la barre latérale",
"hideOnThisPage": "Masquer la barre latérale sur cette page",
"showSidebar": "Afficher la barre latérale",
"sidebarHiddenNotification": "Barre latérale masquée sur la page {page}",
"switchToListView": "Passer en vue liste",
"switchToTreeView": "Passer en vue arborescence",
"recursiveOn": "Inclure les sous-dossiers",

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "שורש",
"moreOptions": "אפשרויות נוספות",
"collapseAll": "כווץ את כל התיקיות",
"pinSidebar": "נעל סרגל צד",
"unpinSidebar": "שחרר סרגל צד",
"hideOnThisPage": "הסתר סרגל צד בדף זה",
"showSidebar": "הצג סרגל צד",
"sidebarHiddenNotification": "סרגל הצד מוסתר בדף {page}",
"switchToListView": "עבור לתצוגת רשימה",
"switchToTreeView": "תצוגת עץ",
"recursiveOn": "כלול תיקיות משנה",

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "ルート",
"moreOptions": "その他のオプション",
"collapseAll": "すべてのフォルダを折りたたむ",
"pinSidebar": "サイドバーを固定",
"unpinSidebar": "サイドバーの固定を解除",
"hideOnThisPage": "このページでサイドバーを非表示",
"showSidebar": "サイドバーを表示",
"sidebarHiddenNotification": "{page}ページでサイドバーが非表示になっています",
"switchToListView": "リストビューに切り替え",
"switchToTreeView": "ツリー表示に切り替え",
"recursiveOn": "サブフォルダーを含める",

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "루트",
"moreOptions": "더 많은 옵션",
"collapseAll": "모든 폴더 접기",
"pinSidebar": "사이드바 고정",
"unpinSidebar": "사이드바 고정 해제",
"hideOnThisPage": "이 페이지에서 사이드바 숨기기",
"showSidebar": "사이드바 표시",
"sidebarHiddenNotification": "{page} 페이지에서 사이드바가 숨겨져 있습니다",
"switchToListView": "목록 보기로 전환",
"switchToTreeView": "트리 보기로 전환",
"recursiveOn": "하위 폴더 포함",

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "Корень",
"moreOptions": "Дополнительные параметры",
"collapseAll": "Свернуть все папки",
"pinSidebar": "Закрепить боковую панель",
"unpinSidebar": "Открепить боковую панель",
"hideOnThisPage": "Скрыть боковую панель на этой странице",
"showSidebar": "Показать боковую панель",
"sidebarHiddenNotification": "Боковая панель скрыта на странице {page}",
"switchToListView": "Переключить на вид списка",
"switchToTreeView": "Переключить на древовидный вид",
"recursiveOn": "Включать вложенные папки",

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "根目录",
"moreOptions": "更多选项",
"collapseAll": "折叠所有文件夹",
"pinSidebar": "固定侧边栏",
"unpinSidebar": "取消固定侧边栏",
"hideOnThisPage": "隐藏此页面侧边栏",
"showSidebar": "显示侧边栏",
"sidebarHiddenNotification": "{page}页面的文件夹侧边栏已隐藏",
"switchToListView": "切换到列表视图",
"switchToTreeView": "切换到树状视图",
"recursiveOn": "包含子文件夹",

View File

@@ -953,9 +953,13 @@
},
"sidebar": {
"modelRoot": "根目錄",
"moreOptions": "更多選項",
"collapseAll": "全部摺疊資料夾",
"pinSidebar": "固定側邊欄",
"unpinSidebar": "取消固定側邊欄",
"hideOnThisPage": "隱藏此頁面側邊欄",
"showSidebar": "顯示側邊欄",
"sidebarHiddenNotification": "{page}頁面的資料夾側邊欄已隱藏",
"switchToListView": "切換至列表檢視",
"switchToTreeView": "切換到樹狀檢視",
"recursiveOn": "包含子資料夾",

View File

@@ -129,6 +129,18 @@ class RecipePersistenceService:
if nsfw_level is not None and isinstance(nsfw_level, int):
recipe_data["preview_nsfw_level"] = nsfw_level
# Compute recipe folder relative to recipes root, mirroring
# RecipeScanner._calculate_folder() which is only called during scan/load.
if recipe_scanner.recipes_dir:
recipe_file_dir = os.path.dirname(normalized_image_path)
try:
relative_folder = os.path.relpath(recipe_file_dir, recipe_scanner.recipes_dir)
if relative_folder in (".", ""):
relative_folder = ""
recipe_data["folder"] = relative_folder.replace(os.path.sep, "/")
except Exception:
recipe_data["folder"] = ""
json_filename = f"{recipe_id}.recipe.json"
json_path = os.path.join(recipes_dir, json_filename)
json_path = os.path.normpath(json_path)

View File

@@ -1,7 +1,7 @@
[project]
name = "comfyui-lora-manager"
description = "Revolutionize your workflow with the ultimate LoRA companion for ComfyUI!"
version = "1.0.11"
version = "1.1.0"
license = {file = "LICENSE"}
dependencies = [
"aiohttp",

View File

@@ -84,6 +84,7 @@
border-bottom: 1px solid var(--border-color);
cursor: pointer;
transition: var(--transition-base);
position: relative;
}
.sidebar-header:hover {
@@ -150,6 +151,120 @@
display: none;
}
/* ===== Sidebar More Options Dropdown ===== */
.sidebar-more-dropdown {
position: absolute;
top: 100%;
right: 8px;
min-width: 190px;
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-xs);
box-shadow: var(--shadow-lg);
z-index: calc(var(--z-overlay) + 20);
display: none;
overflow: hidden;
margin-top: 2px;
}
.sidebar-more-dropdown.open {
display: block;
animation: dropdownFadeIn 0.15s ease;
}
@keyframes dropdownFadeIn {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.sidebar-dropdown-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
cursor: pointer;
font-size: 0.85em;
color: var(--text-color);
transition: var(--transition-base);
white-space: nowrap;
}
.sidebar-dropdown-item:hover {
background: var(--lora-surface);
}
.sidebar-dropdown-item i {
width: 16px;
text-align: center;
color: var(--text-muted);
font-size: 0.9em;
flex-shrink: 0;
}
.sidebar-dropdown-item:hover i {
color: var(--text-color);
}
.sidebar-dropdown-item.disabled {
opacity: 0.4;
pointer-events: none;
}
/* ===== Sidebar Hidden Indicator (left edge) ===== */
.sidebar-hidden-indicator {
position: fixed;
left: 0;
top: 50%;
transform: translateY(-50%);
z-index: var(--z-overlay);
width: 14px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background: var(--border-color);
opacity: 0.3;
border-radius: 0 4px 4px 0;
cursor: pointer;
transition: opacity 0.15s ease, background 0.15s ease;
}
.sidebar-hidden-indicator:hover {
opacity: 0.7;
background: var(--lora-accent);
}
.sidebar-hidden-indicator i {
font-size: 9px;
color: var(--text-muted);
transition: color 0.15s ease;
}
.sidebar-hidden-indicator:hover i {
color: white;
}
.sidebar-hidden-indicator-tooltip {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: 8px;
padding: 4px 10px;
background: var(--text-color);
color: var(--bg-color);
font-size: 0.8em;
border-radius: 4px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s ease;
}
.sidebar-hidden-indicator:hover .sidebar-hidden-indicator-tooltip {
opacity: 1;
}
.sidebar-content {
flex: 1;
overflow: hidden;

View File

@@ -36,6 +36,8 @@ export class SidebarManager {
this.currentDropTarget = null;
this.lastPageControls = null;
this.isDisabledBySetting = false;
this.isDisabledByPage = false;
this.isMoreDropdownOpen = false;
this.initializationPromise = null;
this.isCreatingFolder = false;
this._pendingDragState = null; // 用于保存拖拽创建文件夹时的状态
@@ -68,6 +70,10 @@ export class SidebarManager {
this.handleSidebarDrop = this.handleSidebarDrop.bind(this);
this.handleCreateFolderSubmit = this.handleCreateFolderSubmit.bind(this);
this.handleCreateFolderCancel = this.handleCreateFolderCancel.bind(this);
this.handleMoreToggle = this.handleMoreToggle.bind(this);
this.handleMoreDropdownItemClick = this.handleMoreDropdownItemClick.bind(this);
this.handleDocumentClickForMore = this.handleDocumentClickForMore.bind(this);
this.getPageDisplayName = this.getPageDisplayName.bind(this);
}
setHostPageControls(pageControls) {
@@ -100,6 +106,8 @@ export class SidebarManager {
this.initializeDragAndDrop();
this.updateSidebarTitle();
this.restoreSidebarState();
// Re-apply DOM visibility now that per-page state is known
this.updateDomVisibility(!this.isDisabledBySetting);
await this.loadFolderTree();
if (this.isDisabledBySetting && !forceInitialize) {
this.cleanup();
@@ -143,6 +151,13 @@ export class SidebarManager {
this.sidebarDragHandlersInitialized = false;
}
const moreDropdown = document.getElementById('sidebarMoreDropdown');
if (moreDropdown) {
moreDropdown.classList.remove('open');
}
this.isMoreDropdownOpen = false;
this.hideSidebarHiddenIndicator();
// Reset state
this.pageControls = null;
this.pageType = null;
@@ -151,6 +166,7 @@ export class SidebarManager {
this.expandedNodes = new Set();
this.openDropdown = null;
this.isHovering = false;
this.isDisabledByPage = false;
this.apiClient = null;
this.isInitialized = false;
this.recursiveSearchEnabled = true;
@@ -217,6 +233,18 @@ export class SidebarManager {
if (recursiveToggleBtn) {
recursiveToggleBtn.removeEventListener('click', this.handleRecursiveToggle);
}
const moreToggle = document.getElementById('sidebarMoreToggle');
if (moreToggle) {
moreToggle.removeEventListener('click', this.handleMoreToggle);
}
const moreDropdown = document.getElementById('sidebarMoreDropdown');
if (moreDropdown) {
moreDropdown.removeEventListener('click', this.handleMoreDropdownItemClick);
}
document.removeEventListener('click', this.handleDocumentClickForMore);
}
initializeDragAndDrop() {
@@ -1045,6 +1073,19 @@ export class SidebarManager {
}
});
}
// More options dropdown
const moreToggle = document.getElementById('sidebarMoreToggle');
if (moreToggle) {
moreToggle.addEventListener('click', this.handleMoreToggle);
}
const moreDropdown = document.getElementById('sidebarMoreDropdown');
if (moreDropdown) {
moreDropdown.addEventListener('click', this.handleMoreDropdownItemClick);
}
document.addEventListener('click', this.handleDocumentClickForMore);
}
handleDocumentClick(event) {
@@ -1066,6 +1107,7 @@ export class SidebarManager {
this.isPinned = !this.isPinned;
this.updateAutoHideState();
this.updatePinButton();
this.updateMoreDropdownLabels();
this.saveSidebarState();
this.updateContainerMargin();
}
@@ -1129,7 +1171,7 @@ export class SidebarManager {
}
updateAutoHideState() {
if (this.isDisabledBySetting) return;
if (this.isDisabledBySetting || this.isDisabledByPage) return;
const sidebar = document.getElementById('folderSidebar');
const hoverArea = document.getElementById('sidebarHoverArea');
@@ -1174,9 +1216,12 @@ export class SidebarManager {
if (!container || !sidebar || this.isDisabledBySetting) return;
// Reset margin to default
// Always reset margin first — needed when transitioning from visible to hidden
container.style.marginLeft = '';
// When per-page disabled, skip adjustment but margin is already reset
if (this.isDisabledByPage) return;
// Only adjust margin if sidebar is visible and pinned
if ((this.isPinned || this.isHovering) && this.isVisible) {
const sidebarWidth = sidebar.offsetWidth;
@@ -1193,20 +1238,29 @@ export class SidebarManager {
}
updateDomVisibility(enabled) {
// Per-page disable adds on top of global setting
const isVisible = enabled && !this.isDisabledByPage;
const sidebar = document.getElementById('folderSidebar');
const hoverArea = document.getElementById('sidebarHoverArea');
if (sidebar) {
sidebar.classList.toggle('hidden-by-setting', !enabled);
sidebar.setAttribute('aria-hidden', (!enabled).toString());
sidebar.classList.toggle('hidden-by-setting', !isVisible);
sidebar.setAttribute('aria-hidden', (!isVisible).toString());
}
if (hoverArea) {
hoverArea.classList.toggle('hidden-by-setting', !enabled);
if (!enabled) {
hoverArea.classList.toggle('hidden-by-setting', !isVisible);
if (!isVisible) {
hoverArea.classList.add('disabled');
}
}
// Show or hide the "sidebar hidden" notification
if (enabled && this.isDisabledByPage) {
this.showSidebarHiddenIndicator();
} else {
this.hideSidebarHiddenIndicator();
}
}
async setSidebarEnabled(enabled) {
@@ -1266,6 +1320,133 @@ export class SidebarManager {
}
}
// ===== More Options Dropdown =====
handleMoreToggle(event) {
event.stopPropagation();
const dropdown = document.getElementById('sidebarMoreDropdown');
if (!dropdown) return;
this.isMoreDropdownOpen = !dropdown.classList.contains('open');
dropdown.classList.toggle('open', this.isMoreDropdownOpen);
this.updateMoreDropdownLabels();
}
handleMoreDropdownItemClick(event) {
const item = event.target.closest('.sidebar-dropdown-item');
if (!item) return;
const action = item.dataset.action;
if (!action) return;
const dropdown = document.getElementById('sidebarMoreDropdown');
if (dropdown) {
dropdown.classList.remove('open');
this.isMoreDropdownOpen = false;
}
switch (action) {
case 'toggle-pin':
this.handlePinToggle(event);
break;
case 'toggle-hide':
this.toggleHideOnThisPage();
break;
}
}
handleDocumentClickForMore(event) {
const dropdown = document.getElementById('sidebarMoreDropdown');
const toggle = document.getElementById('sidebarMoreToggle');
if (!dropdown || !toggle) return;
if (!dropdown.contains(event.target) && !toggle.contains(event.target)) {
dropdown.classList.remove('open');
this.isMoreDropdownOpen = false;
}
}
updateMoreDropdownLabels() {
const pinLabel = document.getElementById('sidebarMorePinLabel');
if (pinLabel) {
pinLabel.textContent = this.isPinned
? translate('sidebar.unpinSidebar')
: translate('sidebar.pinSidebar');
}
const hideItem = document.querySelector('.sidebar-dropdown-item[data-action="toggle-hide"]');
if (hideItem) {
const hideIcon = hideItem.querySelector('i');
const hideLabel = hideItem.querySelector('span');
if (this.isDisabledByPage) {
hideLabel.textContent = translate('sidebar.showSidebar');
if (hideIcon) {
hideIcon.className = 'fas fa-eye';
}
} else {
hideLabel.textContent = translate('sidebar.hideOnThisPage');
if (hideIcon) {
hideIcon.className = 'fas fa-eye-slash';
}
}
}
}
toggleHideOnThisPage() {
this.isDisabledByPage = !this.isDisabledByPage;
setStorageItem(`${this.pageType}_sidebarDisabled`, this.isDisabledByPage);
this.updateDomVisibility(!this.isDisabledBySetting);
this.updateAutoHideState();
this.updateContainerMargin();
this.updateMoreDropdownLabels();
if (!this.isDisabledByPage) {
this.hideSidebarHiddenIndicator();
} else {
showToast(
'sidebar.sidebarHiddenNotification',
{ page: this.getPageDisplayName() },
'info',
`Sidebar hidden on ${this.getPageDisplayName()} page`
);
}
}
getPageDisplayName() {
const names = {
loras: 'LoRAs',
recipes: 'Recipes',
checkpoints: 'Checkpoints',
embeddings: 'Embeddings',
};
return names[this.pageType] || this.pageType;
}
showSidebarHiddenIndicator() {
if (document.getElementById('sidebarHiddenIndicator')) return;
const indicator = document.createElement('div');
indicator.id = 'sidebarHiddenIndicator';
indicator.className = 'sidebar-hidden-indicator';
indicator.innerHTML = `
<i class="fas fa-chevron-right"></i>
<span class="sidebar-hidden-indicator-tooltip">${translate('sidebar.showSidebar')}</span>
`;
indicator.addEventListener('click', () => {
this.toggleHideOnThisPage();
});
document.body.appendChild(indicator);
}
hideSidebarHiddenIndicator() {
const indicator = document.getElementById('sidebarHiddenIndicator');
if (indicator) {
indicator.remove();
}
}
async loadFolderTree() {
try {
if (this.displayMode === 'tree') {
@@ -1911,6 +2092,7 @@ export class SidebarManager {
const expandedPaths = getStorageItem(`${this.pageType}_expandedNodes`, []);
const displayMode = getStorageItem(`${this.pageType}_displayMode`, 'tree'); // 'tree' or 'list', default to 'tree'
const recursiveSearchEnabled = getStorageItem(`${this.pageType}_recursiveSearch`, true);
this.isDisabledByPage = getStorageItem(`${this.pageType}_sidebarDisabled`, false);
this.isPinned = isPinned;
this.expandedNodes = new Set(expandedPaths);

View File

@@ -18,6 +18,20 @@
<button class="sidebar-action-btn" id="sidebarPinToggle" title="{{ t('sidebar.unpinSidebar') }}">
<i class="fas fa-thumbtack"></i>
</button>
<button class="sidebar-action-btn" id="sidebarMoreToggle" title="{{ t('sidebar.moreOptions') }}">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<!-- Dropdown menu for more options -->
<div class="sidebar-more-dropdown" id="sidebarMoreDropdown">
<div class="sidebar-dropdown-item" data-action="toggle-pin">
<i class="fas fa-thumbtack"></i>
<span id="sidebarMorePinLabel">{{ t('sidebar.pinSidebar') }}</span>
</div>
<div class="sidebar-dropdown-item" data-action="toggle-hide">
<i class="fas fa-eye-slash"></i>
<span>{{ t('sidebar.hideOnThisPage') }}</span>
</div>
</div>
</div>
<div class="sidebar-content">