From f0cc94d96f98129a10a6eff95bd0777d9067334f Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Mon, 20 Apr 2026 12:49:12 +0000 Subject: [PATCH] form: hide sidebar by default; icons: swap like/comment sprites Form sidebar is hidden for saved docs by default; user's explicit toggle persists via localStorage (jey-form-sidebar). Swap Frappe's hard-coded sprite refs on render: #icon-heart -> #icon-star (like action) #es-line-chat-alt -> #jey-icon-comment (local sprite, 2-line bubble) Injected sprite + MutationObserver keep swaps applied as nodes stream in. Co-Authored-By: Claude Opus 4.7 (1M context) --- jey_theme/public/js/jey_theme.js | 134 +++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/jey_theme/public/js/jey_theme.js b/jey_theme/public/js/jey_theme.js index fa478f0..c23d19f 100644 --- a/jey_theme/public/js/jey_theme.js +++ b/jey_theme/public/js/jey_theme.js @@ -817,6 +817,49 @@ observer.observe(document.body, { childList: true, subtree: true }); })(); +/* ========================================================================== + Form sidebar: hidden by default + Frappe shows the form sidebar for saved docs; we hide it by default and + remember the user's explicit toggle via localStorage. + ========================================================================== */ +(function () { + "use strict"; + + var STORAGE_KEY = "jey-form-sidebar"; // "hidden" | "visible" + + function getPreference() { + return localStorage.getItem(STORAGE_KEY) === "visible" ? "visible" : "hidden"; + } + + function applyPreference(frm) { + if (!frm || !frm.sidebar || !frm.sidebar.sidebar) return; + if (frm.doc && frm.doc.__islocal) return; // Frappe already hides new docs + var wrap = frm.sidebar.sidebar.parent(); + if (!wrap || !wrap.length) return; + var page_sidebar = frm.sidebar.page && frm.sidebar.page.sidebar; + if (getPreference() === "hidden") { + wrap.hide(); + if (page_sidebar) page_sidebar.addClass("hide-sidebar"); + } else { + wrap.show(); + if (page_sidebar) page_sidebar.removeClass("hide-sidebar"); + } + } + + $(document).on("form-refresh", function (e, frm) { + applyPreference(frm); + }); + + // When user clicks "Toggle Sidebar", persist the new state + $(document.body).on("toggleSidebar", function () { + var frm = window.cur_frm; + if (!frm || !frm.sidebar || !frm.sidebar.sidebar) return; + var wrap = frm.sidebar.sidebar.parent(); + if (!wrap || !wrap.length) return; + localStorage.setItem(STORAGE_KEY, wrap.is(":visible") ? "visible" : "hidden"); + }); +})(); + /* ========================================================================== Reload button next to Notifications in sidebar ========================================================================== */ @@ -2272,3 +2315,94 @@ } }); })(); + + +/* ========================================================================== + Sprite icon swaps + Frappe hard-codes #icon-heart for the "like" action and #es-line-chat-alt + for the comment count in list/form templates. Rewrite the + on render so lists/forms show a star and a comment bubble instead. + + For the comment icon we inject our own sprite symbol (#jey-icon-comment) + because Frappe's bundled #icon-message-square-text is the newer lucide + variant with 3 text lines, and we want the older 2-line version. + ========================================================================== */ +(function () { + var SWAPS = { + "#icon-heart": "#icon-star", + "#es-line-chat-alt": "#jey-icon-comment", + }; + + function injectSprite() { + if (document.getElementById("jey-icon-sprite")) return; + var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.id = "jey-icon-sprite"; + svg.setAttribute("width", "0"); + svg.setAttribute("height", "0"); + svg.setAttribute("aria-hidden", "true"); + svg.style.position = "absolute"; + svg.innerHTML = + '' + + '' + + '' + + '' + + ''; + (document.body || document.documentElement).insertBefore( + svg, (document.body || document.documentElement).firstChild + ); + } + + function swapIn(root) { + if (!root || root.nodeType !== 1) return; + Object.keys(SWAPS).forEach(function (from) { + var to = SWAPS[from]; + // href= and xlink:href= both appear in the wild + var nodes = root.querySelectorAll( + 'use[href="' + from + '"], use[*|href="' + from + '"]' + ); + nodes.forEach(function (u) { + if (u.getAttribute("href") === from) u.setAttribute("href", to); + if (u.getAttributeNS("http://www.w3.org/1999/xlink", "href") === from) { + u.setAttributeNS("http://www.w3.org/1999/xlink", "href", to); + } + }); + }); + } + + function init() { + injectSprite(); + swapIn(document.body); + var obs = new MutationObserver(function (mutations) { + for (var i = 0; i < mutations.length; i++) { + var m = mutations[i]; + if (m.type === "childList") { + m.addedNodes.forEach(function (n) { + if (n.nodeType !== 1) return; + // If the added node itself is a , handle it directly + if (n.tagName && n.tagName.toLowerCase() === "use") { + var href = n.getAttribute("href"); + if (href && SWAPS[href]) n.setAttribute("href", SWAPS[href]); + } + swapIn(n); + }); + } else if (m.type === "attributes" && m.target && m.target.tagName && + m.target.tagName.toLowerCase() === "use") { + var href2 = m.target.getAttribute("href"); + if (href2 && SWAPS[href2]) m.target.setAttribute("href", SWAPS[href2]); + } + } + }); + obs.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ["href", "xlink:href"], + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})();