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) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-04-20 12:49:12 +00:00
parent 373ffd73b8
commit f0cc94d96f
1 changed files with 134 additions and 0 deletions

View File

@ -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 <use href=...>
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 =
'<symbol viewBox="0 0 24 24" id="jey-icon-comment">' +
'<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>' +
'<path d="M13 8H7"/>' +
'<path d="M17 12H7"/>' +
'</symbol>';
(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 <use>, 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();
}
})();