summaryrefslogtreecommitdiffstats
path: root/comm/suite/components/sidebar/content/customize.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/components/sidebar/content/customize.js')
-rw-r--r--comm/suite/components/sidebar/content/customize.js692
1 files changed, 692 insertions, 0 deletions
diff --git a/comm/suite/components/sidebar/content/customize.js b/comm/suite/components/sidebar/content/customize.js
new file mode 100644
index 0000000000..e8400e909c
--- /dev/null
+++ b/comm/suite/components/sidebar/content/customize.js
@@ -0,0 +1,692 @@
+/* -*- Mode: Java; tab-width: 4; insert-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//////////////////////////////////////////////////////////////
+// Import modules
+//////////////////////////////////////////////////////////////
+
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+//////////////////////////////////////////////////////////////
+// Global variables
+//////////////////////////////////////////////////////////////
+
+// Set to true for noise
+const CUST_DEBUG = false;
+
+// the rdf service
+var RDF = Cc["@mozilla.org/rdf/rdf-service;1"]
+ .getService(Ci.nsIRDFService);
+var NC = "http://home.netscape.com/NC-rdf#";
+
+var sidebarObj = new Object;
+var allPanelsObj = new Object;
+var original_panels = new Array();
+
+//////////////////////////////////////////////////////////////
+// Sidebar Init/Destroy
+//////////////////////////////////////////////////////////////
+
+function sidebar_customize_init()
+{
+ allPanelsObj.datasources = window.arguments[0];
+ allPanelsObj.resource = window.arguments[1];
+ sidebarObj.datasource_uri = window.arguments[2];
+ sidebarObj.resource = window.arguments[3];
+
+ debug("Init: all panels datasources = " + allPanelsObj.datasources);
+ debug("Init: all panels resource = " + allPanelsObj.resource);
+ debug("Init: sidebarObj.datasource_uri = " + sidebarObj.datasource_uri);
+ debug("Init: sidebarObj.resource = " + sidebarObj.resource);
+
+ var all_panels = document.getElementById('other-panels');
+ var current_panels = document.getElementById('current-panels');
+
+ debug("Adding observer to all panels database.");
+ all_panels.database.AddObserver(panels_observer);
+
+ allPanelsObj.datasources = allPanelsObj.datasources.trim().split(/\s+/);
+ for (var ii = 0; ii < allPanelsObj.datasources.length; ii++) {
+ debug("Init: Adding "+allPanelsObj.datasources[ii]);
+
+ // This will load the datasource, if it isn't already.
+ var datasource = RDF.GetDataSource(allPanelsObj.datasources[ii]);
+ all_panels.database.AddDataSource(datasource);
+ }
+
+ // Add the datasource for current list of panels. It selects panels out
+ // of the other datasources.
+ debug("Init: Adding current panels, "+sidebarObj.datasource_uri);
+ sidebarObj.datasource = RDF.GetDataSource(sidebarObj.datasource_uri);
+
+ // Root the customize dialog at the correct place.
+ debug("Init: reset all panels ref, "+allPanelsObj.resource);
+ all_panels.setAttribute('ref', allPanelsObj.resource);
+
+ // Create a "container" wrapper around the current panels to
+ // manipulate the RDF:Seq more easily.
+ var panel_list = sidebarObj.datasource.GetTarget(RDF.GetResource(sidebarObj.resource), RDF.GetResource(NC + "panel-list"), true);
+ sidebarObj.container = Cc["@mozilla.org/rdf/container;1"].createInstance(Ci.nsIRDFContainer);
+ sidebarObj.container.Init(sidebarObj.datasource, panel_list);
+
+ // Add all the current panels to the tree
+ current_panels = sidebarObj.container.GetElements();
+ while (current_panels.hasMoreElements()) {
+ var panel = current_panels.getNext().QueryInterface(Ci.nsIRDFResource);
+ if (add_node_to_current_list(sidebarObj.datasource, panel) >= 0) {
+ original_panels.push(panel.Value);
+ original_panels[panel.Value] = true;
+ }
+ }
+
+ var links =
+ all_panels.database.GetSources(RDF.GetResource(NC + "haslink"),
+ RDF.GetLiteral("true"), true);
+
+ while (links.hasMoreElements()) {
+ var folder =
+ links.getNext().QueryInterface(Ci.nsIRDFResource);
+ var folder_name = folder.Value;
+ debug("+++ fixing up remote container " + folder_name + "\n");
+ fixup_remote_container(folder_name);
+ }
+
+ sizeToContent();
+}
+
+function sidebar_customize_destruct()
+{
+ var all_panels = document.getElementById('other-panels');
+ debug("Removing observer from all_panels database.");
+ all_panels.database.RemoveObserver(panels_observer);
+}
+
+
+//////////////////////////////////////////////////////////////////
+// Panels' RDF Datasource Observer
+//////////////////////////////////////////////////////////////////
+var panels_observer = {
+ onAssert : function(ds,src,prop,target) {
+ //debug ("observer: assert");
+ // "refresh" is asserted by select menu and by customize.js.
+ if (prop == RDF.GetResource(NC + "link")) {
+ setTimeout(fixup_remote_container, 100, src.Value);
+ }
+ },
+ onUnassert : function(ds,src,prop,target) {
+ //debug ("observer: unassert");
+ },
+ onChange : function(ds,src,prop,old_target,new_target) {
+ //debug ("observer: change");
+ },
+ onMove : function(ds,old_src,new_src,prop,target) {
+ //debug ("observer: move");
+ },
+ onBeginUpdateBatch : function(ds) {
+ //debug ("observer: onBeginUpdateBatch");
+ },
+ onEndUpdateBatch : function(ds) {
+ //debug ("observer: onEndUpdateBatch");
+ }
+};
+
+function fixup_remote_container(id)
+{
+ debug('fixup_remote_container('+id+')');
+
+ var container = document.getElementById(id);
+ if (container) {
+ container.setAttribute('container', 'true');
+ container.removeAttribute('open');
+ }
+}
+
+function fixup_children(id) {
+ // Add container="true" on nodes with "link" attribute
+ var treeitem = document.getElementById(id);
+
+ var children = treeitem.childNodes.item(1).childNodes;
+ for (var ii=0; ii < children.length; ii++) {
+ var child = children.item(ii);
+ if (child.getAttribute('link') != '' &&
+ child.getAttribute('container') != 'true') {
+ child.setAttribute('container', 'true');
+ child.removeAttribute('open');
+ }
+ }
+}
+
+function get_attr(registry,service,attr_name)
+{
+ var attr = registry.GetTarget(service,
+ RDF.GetResource(NC + attr_name),
+ true);
+ if (attr)
+ attr = attr.QueryInterface(Ci.nsIRDFLiteral);
+ if (attr)
+ attr = attr.Value;
+ return attr;
+}
+
+function SelectChangeForOtherPanels(event, target)
+{
+ enable_buttons_for_other_panels();
+}
+
+function ClickOnOtherPanels(event)
+{
+ var tree = document.getElementById("other-panels");
+
+ var rowIndex = -1;
+ if (event.type == "click" && event.button == 0) {
+ var b = tree.treeBoxObject;
+ var cell = b.getCellAt(event.clientX, event.clientY);
+
+ if (cell.childElt == "twisty" || event.detail == 2) {
+ rowIndex = cell.row;
+ }
+ }
+
+ if (rowIndex < 0) return;
+
+ var treeitem = tree.contentView.getItemAtIndex(rowIndex);
+ var res = RDF.GetResource(treeitem.id);
+
+ if (treeitem.getAttribute('container') == 'true') {
+ if (treeitem.getAttribute('open') == 'true') {
+ var link = treeitem.getAttribute('link');
+ var loaded_link = treeitem.getAttribute('loaded_link');
+ if (link != '' && !loaded_link) {
+ debug("Has remote datasource: "+link);
+ add_datasource_to_other_panels(link);
+ treeitem.setAttribute('loaded_link', 'true');
+ } else {
+ setTimeout(fixup_children, 100, treeitem.getAttribute('id'));
+ }
+ }
+ }
+
+ // Remove the selection in the "current" panels list
+ var current_panels = document.getElementById('current-panels');
+ current_panels.view.selection.clearSelection();
+ enable_buttons_for_current_panels();
+}
+
+function add_datasource_to_other_panels(link) {
+ // Convert the |link| attribute into a URL
+ var url = document.location;
+ debug("Current URL: " +url);
+ debug("Current link: " +link);
+
+ var uri = Cc['@mozilla.org/network/standard-url;1'].createInstance();
+ uri = uri.QueryInterface(Ci.nsIURI);
+ uri.spec = url;
+ uri = uri.resolve(link);
+
+ debug("New URL: " +uri);
+
+ // Add the datasource to the tree
+ var all_panels = document.getElementById('other-panels');
+ all_panels.database.AddDataSource(RDF.GetDataSource(uri));
+
+ // XXX This is a hack to force re-display
+ //all_panels.setAttribute('ref', allPanelsObj.resource);
+}
+
+// Handle a selection change in the current panels.
+function SelectChangeForCurrentPanels() {
+ // Remove the selection in the available panels list
+ var all_panels = document.getElementById('other-panels');
+ all_panels.view.selection.clearSelection();
+
+ enable_buttons_for_current_panels();
+}
+
+// Move the selected item up the the current panels list.
+function MoveUp() {
+ var tree = document.getElementById('current-panels');
+ if (tree.view.selection.count == 1) {
+ var index = tree.currentIndex;
+ var selected = tree.contentView.getItemAtIndex(index);
+ var before = selected.previousSibling;
+ if (before) {
+ selected.remove();
+ before.parentNode.insertBefore(selected, before);
+ tree.view.selection.select(index-1);
+ tree.treeBoxObject.ensureRowIsVisible(index-1);
+ }
+ }
+}
+
+// Move the selected item down the the current panels list.
+function MoveDown() {
+ var tree = document.getElementById('current-panels');
+ if (tree.view.selection.count == 1) {
+ var index = tree.currentIndex;
+ var selected = tree.contentView.getItemAtIndex(index);
+ if (selected.nextSibling) {
+ if (selected.nextSibling.nextSibling)
+ selected.parentNode.insertBefore(selected, selected.nextSibling.nextSibling);
+ else
+ selected.parentNode.appendChild(selected);
+ tree.view.selection.select(index+1);
+ tree.treeBoxObject.ensureRowIsVisible(index+1);
+ }
+ }
+}
+
+function PreviewPanel()
+{
+ var tree = document.getElementById('other-panels');
+ var database = tree.database;
+ var sel = tree.view.selection;
+ var rangeCount = sel.getRangeCount();
+ for (var range = 0; range < rangeCount; ++range) {
+ var min = {}, max = {};
+ sel.getRangeAt(range, min, max);
+ for (var index = min.value; index <= max.value; ++index) {
+ var item = tree.contentView.getItemAtIndex(index);
+ var res = RDF.GetResource(item.id);
+
+ var preview_name = get_attr(database, res, 'title');
+ var preview_URL = get_attr(database, res, 'content');
+ if (!preview_URL || !preview_name) continue;
+
+ window.openDialog("chrome://communicator/content/sidebar/preview.xul",
+ "_blank", "chrome,resizable,close,dialog=no",
+ preview_name, preview_URL);
+ }
+ }
+}
+
+// Add the selected panel(s).
+function AddPanel()
+{
+ var added = -1;
+
+ var tree = document.getElementById('other-panels');
+ var database = tree.database;
+ var sel = tree.view.selection;
+ var ranges = sel.getRangeCount();
+ for (var range = 0; range < ranges; ++range) {
+ var min = {}, max = {};
+ sel.getRangeAt(range, min, max);
+ for (var index = min.value; index <= max.value; ++index) {
+ var item = tree.contentView.getItemAtIndex(index);
+ if (item.getAttribute("container") != "true") {
+ var res = RDF.GetResource(item.id);
+ // Add the panel to the current list.
+ added = add_node_to_current_list(database, res);
+ }
+ }
+ }
+
+ if (added >= 0) {
+ // Remove the selection in the other list.
+ // Selection will move to "current" list.
+ tree.view.selection.clearSelection();
+
+ var current_panels = document.getElementById('current-panels');
+ current_panels.view.selection.select(added);
+ current_panels.treeBoxObject.ensureRowIsVisible(added);
+ }
+}
+
+// Copy a panel node into a database such as the current panel list.
+function add_node_to_current_list(registry, service)
+{
+ debug("Adding "+service.Value);
+
+ // Copy out the attributes we want
+ var option_title = get_attr(registry, service, 'title');
+ var option_customize = get_attr(registry, service, 'customize');
+ var option_content = get_attr(registry, service, 'content');
+ if (!option_title || !option_content)
+ return -1;
+
+ var tree = document.getElementById('current-panels');
+ var tree_root = tree.lastChild;
+
+ // Check to see if the panel already exists...
+ var i = 0;
+ for (var treeitem = tree_root.firstChild; treeitem; treeitem = treeitem.nextSibling) {
+ if (treeitem.id == service.Value)
+ // The panel is already in the current panel list.
+ // Avoid adding it twice.
+ return i;
+ ++i;
+ }
+
+ // Create a treerow for the new panel
+ var item = document.createElement('treeitem');
+ var row = document.createElement('treerow');
+ var cell = document.createElement('treecell');
+
+ // Copy over the attributes
+ item.setAttribute('id', service.Value);
+ cell.setAttribute('label', option_title);
+
+ // Add it to the current panels tree
+ item.appendChild(row);
+ row.appendChild(cell);
+ tree_root.appendChild(item);
+ return i;
+}
+
+// Remove the selected panel(s) from the current list tree.
+function RemovePanel()
+{
+ var tree = document.getElementById('current-panels');
+ var sel = tree.view.selection;
+
+ var nextNode = -1;
+ var rangeCount = sel.getRangeCount();
+ for (var range = rangeCount-1; range >= 0; --range) {
+ var min = {}, max = {};
+ sel.getRangeAt(range, min, max);
+ for (var index = max.value; index >= min.value; --index) {
+ var item = tree.contentView.getItemAtIndex(index);
+ nextNode = item.nextSibling ? index : -1;
+ item.remove();
+ }
+ }
+
+ if (nextNode >= 0)
+ sel.select(nextNode);
+}
+
+// Bring up a new window with the customize url
+// for an individual panel.
+function CustomizePanel()
+{
+ var tree = document.getElementById('current-panels');
+ var numSelected = tree.view.selection.count;
+
+ if (numSelected == 1) {
+ var index = tree.currentIndex;
+ var selectedNode = tree.contentView.getItemAtIndex(index);
+ var panel_id = selectedNode.getAttribute('id');
+ var customize_url = selectedNode.getAttribute('customize');
+
+ debug("url = " + customize_url);
+
+ if (!customize_url) return;
+
+ window.openDialog('chrome://communicator/content/sidebar/customize-panel.xul',
+ '_blank',
+ 'chrome,resizable,width=690,height=600,dialog=no,close',
+ panel_id,
+ customize_url,
+ sidebarObj.datasource_uri,
+ sidebarObj.resource);
+ }
+}
+
+function BrowseMorePanels()
+{
+ var url = '';
+ var browser_url = "chrome://navigator/content/navigator.xul";
+ var locale;
+ try {
+ url = Services.prefs.getCharPref("sidebar.customize.more_panels.url");
+ var temp = Services.prefs.getCharPref("browser.chromeURL");
+ if (temp)
+ browser_url = temp;
+ } catch(ex) {
+ debug("Unable to get prefs: "+ex);
+ }
+ window.openDialog(browser_url, "_blank", "chrome,all,dialog=no", url);
+}
+
+function customize_getBrowserURL()
+{
+ return url;
+}
+
+// Serialize the new list of panels.
+function Save()
+{
+ persist_dialog_dimensions();
+
+ var all_panels = document.getElementById('other-panels');
+ var current_panels = document.getElementById('current-panels');
+
+ // See if list membership has changed
+ var panels = [];
+ var tree_root = current_panels.lastChild.childNodes;
+ var list_unchanged = (tree_root.length == original_panels.length);
+ for (var i = 0; i < tree_root.length; i++) {
+ var panel = tree_root[i].id;
+ panels.push(panel);
+ panels[panel] = true;
+ if (list_unchanged && original_panels[i] != panel)
+ list_unchanged = false;
+ }
+ if (list_unchanged)
+ return;
+
+ // Remove all the current panels from the datasource.
+ current_panels = sidebarObj.container.GetElements();
+ while (current_panels.hasMoreElements()) {
+ panel = current_panels.getNext().QueryInterface(Ci.nsIRDFResource);
+
+ // "Check if the item is one of the broadcaster panels imported to RDF from
+ // mainBroadcasterSet. If so, then don't remove it from datasource.
+ var master_list = sidebarObj.datasource.GetTarget(RDF.GetResource(allPanelsObj.resource), RDF.GetResource(NC + "panel-list"), true);
+ var masterSeq = Cc["@mozilla.org/rdf/container;1"]
+ .createInstance(Ci.nsIRDFContainer);
+ masterSeq.Init(sidebarObj.datasource, master_list);
+ var inmaster = (masterSeq.IndexOf(panel) != -1);
+
+ if (panel.Value in panels || inmaster) {
+ // This panel will remain in the sidebar.
+ // Remove the resource, but keep all the other attributes.
+ // Removing it will allow it to be added in the correct order.
+ // Saving the attributes will preserve things such as the exclude state.
+ sidebarObj.container.RemoveElement(panel, false);
+ } else {
+ // Kiss it goodbye.
+ delete_resource_deeply(sidebarObj.container, panel);
+ }
+ }
+
+ // Add the new list of panels
+ for (var ii = 0; ii < panels.length; ++ii) {
+ var id = panels[ii];
+ var resource = RDF.GetResource(id);
+ if (id in original_panels) {
+ sidebarObj.container.AppendElement(resource);
+ } else {
+ copy_resource_deeply(all_panels.database, resource, sidebarObj.container);
+ }
+ }
+ refresh_all_sidebars();
+
+ // Write the modified panels out.
+ sidebarObj.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
+}
+
+// Search for an element in an array
+function has_element(array, element) {
+ for (var ii=0; ii < array.length; ii++) {
+ if (array[ii] == element) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Search for targets from resource in datasource
+function has_targets(datasource, resource) {
+ var arcs = datasource.ArcLabelsOut(resource);
+ return arcs.hasMoreElements();
+}
+
+// Use an assertion to pass a "refresh" event to all the sidebars.
+// They use observers to watch for this assertion (in sidebarOverlay.js).
+function refresh_all_sidebars() {
+ sidebarObj.datasource.Assert(RDF.GetResource(sidebarObj.resource),
+ RDF.GetResource(NC + "refresh"),
+ RDF.GetLiteral("true"),
+ true);
+ sidebarObj.datasource.Unassert(RDF.GetResource(sidebarObj.resource),
+ RDF.GetResource(NC + "refresh"),
+ RDF.GetLiteral("true"));
+}
+
+// Remove a resource and all the arcs out from it.
+function delete_resource_deeply(container, resource) {
+ var arcs = container.DataSource.ArcLabelsOut(resource);
+ while (arcs.hasMoreElements()) {
+ var arc = arcs.getNext();
+ var targets = container.DataSource.GetTargets(resource, arc, true);
+ while (targets.hasMoreElements()) {
+ var target = targets.getNext();
+ container.DataSource.Unassert(resource, arc, target, true);
+ }
+ }
+ container.RemoveElement(resource, false);
+}
+
+// Copy a resource and all its arcs out to a new container.
+function copy_resource_deeply(source_datasource, resource, dest_container) {
+ var arcs = source_datasource.ArcLabelsOut(resource);
+ while (arcs.hasMoreElements()) {
+ var arc = arcs.getNext();
+ var targets = source_datasource.GetTargets(resource, arc, true);
+ while (targets.hasMoreElements()) {
+ var target = targets.getNext();
+ dest_container.DataSource.Assert(resource, arc, target, true);
+ }
+ }
+ dest_container.AppendElement(resource);
+}
+
+function enable_buttons_for_other_panels()
+{
+ var add_button = document.getElementById('add_button');
+ var preview_button = document.getElementById('preview_button');
+ var all_panels = document.getElementById('other-panels');
+
+ var sel = all_panels.view.selection;
+ var num_selected = sel ? sel.count : 0;
+ if (sel) {
+ var ranges = sel.getRangeCount();
+ for (var range = 0; range < ranges; ++range) {
+ var min = {}, max = {};
+ sel.getRangeAt(range, min, max);
+ for (var index = min; index <= max; ++index) {
+ var node = all_panels.contentView.getItemAtIndex(index);
+ if (node.getAttribute('container') != 'true') {
+ ++num_selected;
+ }
+ }
+ }
+ }
+
+ if (num_selected > 0) {
+ add_button.removeAttribute('disabled');
+ preview_button.removeAttribute('disabled');
+ } else {
+ add_button.setAttribute('disabled','true');
+ preview_button.setAttribute('disabled','true');
+ }
+}
+
+function enable_buttons_for_current_panels() {
+ var up = document.getElementById('up');
+ var down = document.getElementById('down');
+ var tree = document.getElementById('current-panels');
+ var customize = document.getElementById('customize-button');
+ var remove = document.getElementById('remove-button');
+
+ var numSelected = tree.view.selection.count;
+ var canMoveUp = false, canMoveDown = false, customizeURL = '';
+
+ if (numSelected == 1 && tree.view.selection.isSelected(tree.currentIndex)) {
+ var selectedNode = tree.view.getItemAtIndex(tree.currentIndex);
+ customizeURL = selectedNode.getAttribute('customize');
+ canMoveUp = selectedNode != selectedNode.parentNode.firstChild;
+ canMoveDown = selectedNode != selectedNode.parentNode.lastChild;
+ }
+
+ up.disabled = !canMoveUp;
+ down.disabled = !canMoveDown;
+ customize.disabled = !customizeURL;
+ remove.disabled = !numSelected;
+}
+
+function persist_dialog_dimensions() {
+ // Stole this code from navigator.js to
+ // insure the windows dimensions are saved.
+
+ // Get the current window position/size.
+ var x = window.screenX;
+ var y = window.screenY;
+ var h = window.outerHeight;
+ var w = window.outerWidth;
+
+ // Store these into the window attributes (for persistence).
+ var win = document.getElementById( "main-window" );
+ win.setAttribute( "x", x );
+ win.setAttribute( "y", y );
+ win.setAttribute( "height", h );
+ win.setAttribute( "width", w );
+}
+
+///////////////////////////////////////////////////////////////
+// Handy Debug Tools
+//////////////////////////////////////////////////////////////
+var debug = null;
+var dump_attributes = null;
+var dump_tree = null;
+var _dump_tree_recur = null;
+
+if (!CUST_DEBUG) {
+ debug = function (s) {};
+ dump_attributes = function (node, depth) {};
+ dump_tree = function (node) {};
+ _dump_tree_recur = function (node, depth, index) {};
+} else {
+ debug = function (s) { dump("-*- sb customize: " + s + "\n"); };
+
+ dump_attributes = function (node, depth) {
+ var attributes = node.attributes;
+ var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | . ";
+
+ if (!attributes || attributes.length == 0) {
+ debug(indent.substr(indent.length - depth*2) + "no attributes");
+ }
+ for (var ii=0; ii < attributes.length; ii++) {
+ var attr = attributes.item(ii);
+ debug(indent.substr(indent.length - depth*2) + attr.name +
+ "=" + attr.value);
+ }
+ }
+ dump_tree = function (node) {
+ _dump_tree_recur(node, 0, 0);
+ }
+ _dump_tree_recur = function (node, depth, index) {
+ if (!node) {
+ debug("dump_tree: node is null");
+ }
+ var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | + ";
+ debug(indent.substr(indent.length - depth*2) + index +
+ " " + node.nodeName);
+ if (node.nodeType != Node.TEXT_NODE) {
+ dump_attributes(node, depth);
+ }
+ var kids = node.childNodes;
+ for (var ii=0; ii < kids.length; ii++) {
+ _dump_tree_recur(kids[ii], depth + 1, ii);
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////
+// Install the load/unload handlers
+//////////////////////////////////////////////////////////////
+addEventListener("load", sidebar_customize_init, false);
+addEventListener("unload", sidebar_customize_destruct, false);