diff --git a/css/content.css b/css/content.css
index be25f89..2967250 100644
--- a/css/content.css
+++ b/css/content.css
@@ -268,16 +268,12 @@
.canvasrefined-quiz-safe-dismiss:hover { background: #ececec; }
/* ===== Global Canvas Search ===== */
-.canvasrefined-gs-fab {
- position: fixed; right: 18px; bottom: 18px; z-index: 2147483000;
- width: 46px; height: 46px; border-radius: 50%; border: none; cursor: pointer;
- display: inline-flex; align-items: center; justify-content: center;
- color: #fff; background: var(--bclinks, #0072b5);
- box-shadow: 0 4px 14px rgba(0,0,0,.28);
- transition: transform .12s ease, filter .12s ease;
-}
-.canvasrefined-gs-fab:hover { transform: translateY(-2px) scale(1.04); filter: brightness(1.08); }
-.canvasrefined-gs-fab:active { transform: translateY(0) scale(0.97); }
+/* Native global-nav search item — matches Canvas' own nav links. */
+.canvasrefined-gs-nav-item { list-style: none; }
+.canvasrefined-gs-nav-item .ic-app-header__menu-list-link { cursor: pointer; }
+.canvasrefined-gs-nav-item .menu-item-icon-container svg { width: 26px; height: 26px; }
+.canvasrefined-gs-nav-item:hover .ic-app-header__menu-list-link { background: #0000004f !important; }
+.canvasrefined-gs-nav-item .menu-item__text { font-size: 12px; }
/* Dashboard header trigger — sits just left of the \u22ee options button. */
.canvasrefined-gs-header-btn {
diff --git a/js/content.js b/js/content.js
index 5c24828..962d3a3 100644
--- a/js/content.js
+++ b/js/content.js
@@ -5556,8 +5556,9 @@ function setupGlobalSearch() {
}
function removeGlobalSearch() {
- document.getElementById("canvasrefined-global-search-btn")?.remove();
document.getElementById("canvasrefined-global-search-header-btn")?.remove();
+ removeGlobalSearchBetterSidebarButton();
+ removeGlobalSearchNativeSidebarButton();
closeGlobalSearchModal();
if (_gsPlacementObserver) { _gsPlacementObserver.disconnect(); _gsPlacementObserver = null; }
if (_gsShortcutBound) {
@@ -5566,10 +5567,14 @@ function removeGlobalSearch() {
}
}
-// Placement: on the dashboard the trigger lives in the header (between the
-// "Dashboard" heading and the options menu, right-aligned). Everywhere else it
-// falls back to a floating action button. A rAF-debounced MutationObserver
-// re-evaluates placement as Canvas renders/SPA-navigates.
+// Shared search icon used by the sidebar + header triggers.
+const GLOBAL_SEARCH_ICON_SVG = ``;
+
+// Placement: a search trigger is injected into whichever left sidebar is
+// active — the Better Sidebar (when enabled) or Canvas' native global nav —
+// and, on the dashboard, a button is also placed in the header actions row.
+// There is no floating button. A rAF-debounced MutationObserver re-evaluates
+// placement as Canvas renders/SPA-navigates/rebuilds the sidebar.
let _gsPlacementObserver = null;
let _gsPlacementScheduled = false;
function ensureGlobalSearchButton() {
@@ -5588,36 +5593,98 @@ function ensureGlobalSearchButton() {
function placeGlobalSearchTrigger() {
if (options.global_search !== true) return;
+
+ // Native global nav (the slim icon bar) — always present, so always add.
+ ensureGlobalSearchNativeSidebarButton();
+
+ // Better Sidebar (extra column when the option is enabled) — add when present.
+ const betterSidebar = document.getElementById("better-sidebar-container");
+ if (betterSidebar) {
+ ensureGlobalSearchBetterSidebarButton(betterSidebar);
+ } else {
+ removeGlobalSearchBetterSidebarButton();
+ }
+
+ // Dashboard header button (in addition to the sidebar triggers).
const headerActions = document.querySelector(".ic-Dashboard-header__actions");
if (isDashboardPage() && headerActions) {
ensureGlobalSearchHeaderButton(headerActions);
- document.getElementById("canvasrefined-global-search-btn")?.remove();
} else {
document.getElementById("canvasrefined-global-search-header-btn")?.remove();
- ensureGlobalSearchFab();
}
}
-function ensureGlobalSearchFab() {
- if (document.getElementById("canvasrefined-global-search-btn")) return;
- const btn = document.createElement("button");
- btn.id = "canvasrefined-global-search-btn";
- btn.type = "button";
- btn.className = "canvasrefined-gs-fab";
+// --- Better Sidebar trigger --------------------------------------------------
+
+function ensureGlobalSearchBetterSidebarButton(betterSidebar) {
+ // The first child of #better-sidebar-container is the button list.
+ const sidebarContent = betterSidebar.querySelector("div");
+ if (!sidebarContent) return;
+ if (sidebarContent.querySelector("#canvasrefined-gs-sidebar-btn")) return;
+ const btn = document.createElement("a");
+ btn.id = "canvasrefined-gs-sidebar-btn";
+ btn.className = "canvasrefined-custom-btn better-sidebar-btn canvasrefined-gs-sidebar-btn";
+ btn.href = "#";
btn.title = "Search Canvas (Ctrl+K)";
+ btn.setAttribute("role", "button");
btn.setAttribute("aria-label", "Search Canvas");
- btn.innerHTML = ``;
- btn.addEventListener("click", openGlobalSearchModal);
- if (document.body) {
- document.body.appendChild(btn);
- } else {
- const wait = new MutationObserver(() => {
- if (document.body) { document.body.appendChild(btn); wait.disconnect(); }
- });
- wait.observe(document.documentElement, { childList: true });
- }
+ btn.style.cssText = "width:40%;height:var(--bc-sidebar-btn-height,30px);cursor:pointer;text-align:center;text-decoration:none;display:inline-flex;justify-content:center;align-items:center;gap:var(--bc-sidebar-btn-gap,8px);color:var(--bcsidebar-text) !important;font-weight:bold;position:relative;";
+ btn.innerHTML = `${GLOBAL_SEARCH_ICON_SVG}`;
+ btn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); openGlobalSearchModal(); });
+ // Append so it sits at the bottom of the sidebar item list.
+ sidebarContent.appendChild(btn);
+ // Match the sidebar's current expanded/collapsed mode immediately so the
+ // button doesn't briefly render in the wrong state (e.g. label visible while
+ // collapsed) until the next toggle calls updateSidebar().
+ applyGlobalSearchSidebarButtonMode(btn, betterSidebar.dataset.expanded === "true");
}
+function removeGlobalSearchBetterSidebarButton() {
+ document.getElementById("canvasrefined-gs-sidebar-btn")?.remove();
+}
+
+// Apply the Better Sidebar's current expanded/collapsed styling to the search
+// button, mirroring updateSidebar()'s button/label/svg rules so the button is
+// correct the moment it's inserted (and whenever the sidebar re-renders).
+function applyGlobalSearchSidebarButtonMode(btn, expanded) {
+ if (!btn) return;
+ btn.style.width = expanded ? "80%" : "40%";
+ const label = btn.querySelector(".better-sidebar-label");
+ if (label) label.style.display = expanded ? "block" : "none";
+ btn.querySelectorAll("svg").forEach(svg => {
+ svg.style.width = "var(--bc-sidebar-icon-size,20px)";
+ svg.style.height = "var(--bc-sidebar-icon-size,20px)";
+ });
+}
+
+// --- Native global-nav trigger ----------------------------------------------
+
+function ensureGlobalSearchNativeSidebarButton() {
+ const navMenu = document.getElementById("menu");
+ if (!navMenu) return;
+ if (navMenu.querySelector("#canvasrefined-gs-nav-item")) return;
+ const li = document.createElement("li");
+ li.id = "canvasrefined-gs-nav-item";
+ li.className = "ic-app-header__menu-list-item canvasrefined-gs-nav-item";
+ const link = document.createElement("a");
+ link.className = "ic-app-header__menu-list-link";
+ link.href = "#";
+ link.setAttribute("role", "button");
+ link.title = "Search Canvas (Ctrl+K)";
+ link.setAttribute("aria-label", "Search Canvas");
+ link.innerHTML = ``;
+ link.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); openGlobalSearchModal(); });
+ li.appendChild(link);
+ // Append so the search item appears at the bottom of the global nav.
+ navMenu.appendChild(li);
+}
+
+function removeGlobalSearchNativeSidebarButton() {
+ document.getElementById("canvasrefined-gs-nav-item")?.remove();
+}
+
+// --- Dashboard header trigger -----------------------------------------------
+
function ensureGlobalSearchHeaderButton(headerActions) {
if (headerActions.querySelector("#canvasrefined-global-search-header-btn")) return;
const btn = document.createElement("button");
@@ -5642,14 +5709,19 @@ function ensureGlobalSearchShortcut() {
function onGlobalSearchShortcut(e) {
// Ctrl/Cmd+K toggles the search modal. Ignore when a modal is already open
// and the user is typing in its input (handled by the modal's own listener).
- if ((e.ctrlKey || e.metaKey) && (e.key === "k" || e.key === "K")) {
- const modal = document.getElementById("canvasrefined-global-search-modal");
- if (modal && modal.dataset.open === "true") {
- closeGlobalSearchModal();
- } else {
- e.preventDefault();
- openGlobalSearchModal();
- }
+ if (!(e.ctrlKey || e.metaKey) || !(e.key === "k" || e.key === "K")) return;
+
+ // Never activate on quiz pages (intro or take) so we don't interfere with
+ // the quiz experience or the browser's native Ctrl+K. Read the URL live
+ // because current_page can be stale after Canvas' client-side navigation.
+ if (/^\/courses\/\d+\/quizzes\/\d+(?:\/|$)/.test(window.location.pathname)) return;
+
+ const modal = document.getElementById("canvasrefined-global-search-modal");
+ if (modal && modal.dataset.open === "true") {
+ closeGlobalSearchModal();
+ } else {
+ e.preventDefault();
+ openGlobalSearchModal();
}
}
@@ -5719,11 +5791,6 @@ function openGlobalSearchModal() {
}), 120);
});
- // Block the page scroll while the modal is open.
- const prevOverflow = document.body.style.overflow;
- document.body.style.overflow = "hidden";
- modal._restoreOverflow = () => { document.body.style.overflow = prevOverflow; };
-
requestAnimationFrame(() => input.focus());
// Kick off indexing immediately so the first keystroke is fast.
ensureGlobalSearchIndex();
@@ -5734,7 +5801,6 @@ function openGlobalSearchModal() {
function closeGlobalSearchModal() {
const modal = document.getElementById("canvasrefined-global-search-modal");
if (!modal) return;
- if (typeof modal._restoreOverflow === "function") modal._restoreOverflow();
modal.remove();
}