mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
feat: replace sidebar with svelte alternative
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
import type { SettingsState } from "@/types/storage";
|
||||
import { settingsState } from "../listeners/SettingsState";
|
||||
import { applyMenuItemVisibility } from "../menuItemVisibility";
|
||||
import { ensureAnalyticsMenuOrder } from "@/seqta/utils/sidebarMenuIcons";
|
||||
import { ensureAnalyticsMenuOrder } from "@/seqta/utils/Openers/analyticsMenuOrder";
|
||||
import {
|
||||
isMenuOptionsOpen,
|
||||
setMenuOptionsOpen,
|
||||
} from "@/seqta/utils/Openers/menuOptionsState";
|
||||
import { ChangeMenuItemPositions } from "@/seqta/utils/Openers/menuOrder";
|
||||
import stringToHTML from "../stringToHTML";
|
||||
import Sortable from "sortablejs";
|
||||
|
||||
export let MenuOptionsOpen = false;
|
||||
export { MenuOptionsOpen, setMenuOptionsOpen } from "@/seqta/utils/Openers/menuOptionsState";
|
||||
export { ChangeMenuItemPositions } from "@/seqta/utils/Openers/menuOrder";
|
||||
|
||||
function escapeHtmlAttr(value: string): string {
|
||||
return value
|
||||
@@ -190,14 +196,22 @@ function restoreMenuItemsFromEditMode(
|
||||
}
|
||||
|
||||
export function OpenMenuOptions() {
|
||||
if (MenuOptionsOpen) return;
|
||||
if (isMenuOptionsOpen()) return;
|
||||
|
||||
// Custom Svelte sidebar owns edit mode when mounted.
|
||||
if (document.getElementById("bsplus-sidebar-root")) {
|
||||
void import("@/seqta/ui/sidebar/mountCustomSidebar").then((mod) => {
|
||||
if (mod.openCustomSidebarEditor()) setMenuOptionsOpen(true);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const container = document.getElementById("container");
|
||||
const menu = document.getElementById("menu");
|
||||
if (!container || !menu) return;
|
||||
|
||||
syncDefaultMenuOrder(menu);
|
||||
MenuOptionsOpen = true;
|
||||
setMenuOptionsOpen(true);
|
||||
menu.classList.add("bsplus-sidebar-edit-mode");
|
||||
|
||||
const { cover, menusettings, defaultbutton, savebutton } =
|
||||
@@ -220,7 +234,7 @@ export function OpenMenuOptions() {
|
||||
const closeAll = () => {
|
||||
menusettings.remove();
|
||||
cover.remove();
|
||||
MenuOptionsOpen = false;
|
||||
setMenuOptionsOpen(false);
|
||||
menu.classList.remove("bsplus-sidebar-edit-mode");
|
||||
menu.style.setProperty("--menuHidden", "none");
|
||||
restoreMenuItemsFromEditMode(menu, listItems);
|
||||
@@ -267,34 +281,3 @@ function cloneAttributes(target: any, source: any) {
|
||||
target.setAttribute(attr.nodeName, attr.nodeValue);
|
||||
});
|
||||
}
|
||||
|
||||
export function ChangeMenuItemPositions(menuorder: SettingsState["menuorder"]) {
|
||||
var menuList = document.querySelector("#menu")!.firstChild!.childNodes;
|
||||
|
||||
let listorder = [];
|
||||
for (let i = 0; i < menuList.length; i++) {
|
||||
const menu = menuList[i] as HTMLElement;
|
||||
|
||||
let a = menuorder.indexOf(menu.dataset.key);
|
||||
|
||||
listorder.push(a);
|
||||
}
|
||||
|
||||
var newArr = [];
|
||||
for (var i = 0; i < listorder.length; i++) {
|
||||
const index = listorder[i];
|
||||
if (index >= 0) {
|
||||
newArr[index] = menuList[i];
|
||||
}
|
||||
}
|
||||
|
||||
let listItemsDOM = document.getElementById("menu")!.firstChild;
|
||||
for (let i = 0; i < newArr.length; i++) {
|
||||
const element = newArr[i];
|
||||
if (element) {
|
||||
const elem = element as HTMLElement;
|
||||
elem.setAttribute("data-checked", "true");
|
||||
listItemsDOM!.appendChild(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
|
||||
/** Insert a menu DOM node after the top-level item with `afterKey`. */
|
||||
export function insertMenuItemAfterKey(
|
||||
menuList: HTMLElement,
|
||||
item: HTMLElement,
|
||||
afterKey: string,
|
||||
): void {
|
||||
const after = menuList.querySelector(
|
||||
`:scope > li[data-key="${afterKey}"], :scope > section[data-key="${afterKey}"]`,
|
||||
);
|
||||
if (after instanceof HTMLElement) {
|
||||
after.insertAdjacentElement("afterend", item);
|
||||
} else {
|
||||
menuList.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
export function insertKeyAfterInOrder(
|
||||
order: string[],
|
||||
key: string,
|
||||
afterKey: string,
|
||||
): string[] {
|
||||
if (order.includes(key)) return order;
|
||||
const copy = [...order];
|
||||
const afterIdx = copy.indexOf(afterKey);
|
||||
if (afterIdx >= 0) {
|
||||
copy.splice(afterIdx + 1, 0, key);
|
||||
} else {
|
||||
copy.push(key);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Default Analytics immediately below Courses in saved menu order. */
|
||||
export function ensureAnalyticsMenuOrder(): void {
|
||||
for (const key of ["defaultmenuorder", "menuorder"] as const) {
|
||||
const order = settingsState[key];
|
||||
if (key === "menuorder" && order.length === 0) continue;
|
||||
if (!order.includes("analytics")) {
|
||||
settingsState[key] = insertKeyAfterInOrder(order, "analytics", "courses");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Tiny flag module with no imports — breaks the OpenMenuOptions ↔ sidebarMenuIcons
|
||||
* cycle that caused Vite HMR: "does not provide an export named 'MenuOptionsOpen'".
|
||||
*/
|
||||
export let MenuOptionsOpen = false;
|
||||
|
||||
export function setMenuOptionsOpen(open: boolean) {
|
||||
MenuOptionsOpen = open;
|
||||
}
|
||||
|
||||
export function isMenuOptionsOpen(): boolean {
|
||||
return MenuOptionsOpen;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { SettingsState } from "@/types/storage";
|
||||
|
||||
/** Reorder `#menu > ul` children to match a saved `menuorder` key list. */
|
||||
export function ChangeMenuItemPositions(menuorder: SettingsState["menuorder"]) {
|
||||
const menuRoot = document.querySelector("#menu")?.firstChild;
|
||||
if (!menuRoot) return;
|
||||
|
||||
const menuList = menuRoot.childNodes;
|
||||
const listorder: number[] = [];
|
||||
|
||||
for (let i = 0; i < menuList.length; i++) {
|
||||
const menu = menuList[i] as HTMLElement;
|
||||
listorder.push(menuorder.indexOf(menu.dataset.key));
|
||||
}
|
||||
|
||||
const newArr: ChildNode[] = [];
|
||||
for (let i = 0; i < listorder.length; i++) {
|
||||
const index = listorder[i];
|
||||
if (index >= 0) {
|
||||
newArr[index] = menuList[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < newArr.length; i++) {
|
||||
const element = newArr[i];
|
||||
if (element) {
|
||||
const elem = element as HTMLElement;
|
||||
elem.setAttribute("data-checked", "true");
|
||||
menuRoot.appendChild(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import browser from "webextension-polyfill";
|
||||
|
||||
import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup";
|
||||
import {
|
||||
MenuOptionsOpen,
|
||||
OpenMenuOptions,
|
||||
} from "@/seqta/utils/Openers/OpenMenuOptions";
|
||||
import { OpenMenuOptions } from "@/seqta/utils/Openers/OpenMenuOptions";
|
||||
import { isMenuOptionsOpen } from "@/seqta/utils/Openers/menuOptionsState";
|
||||
|
||||
import sendThemeUpdate from "@/seqta/utils/sendThemeUpdate";
|
||||
import hideSensitiveContent from "@/seqta/ui/dev/hideSensitiveContent";
|
||||
@@ -144,7 +142,7 @@ export class MessageHandler {
|
||||
}
|
||||
|
||||
editSidebar() {
|
||||
if (!MenuOptionsOpen) {
|
||||
if (!isMenuOptionsOpen()) {
|
||||
OpenMenuOptions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { renderShortcuts } from "@/seqta/utils/Render/renderShortcuts";
|
||||
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
||||
import { registerHomeUpcomingSettingsListeners } from "@/seqta/utils/Loaders/LoadHomePage";
|
||||
import { applyMenuItemVisibility } from "@/seqta/utils/menuItemVisibility";
|
||||
import { ChangeMenuItemPositions } from "@/seqta/utils/Openers/OpenMenuOptions";
|
||||
import { ChangeMenuItemPositions } from "@/seqta/utils/Openers/menuOrder";
|
||||
|
||||
import browser from "webextension-polyfill";
|
||||
import type { CustomShortcut } from "@/types/storage";
|
||||
|
||||
@@ -10,7 +10,9 @@ export function applyMenuItemVisibility(): void {
|
||||
let css = "";
|
||||
for (const [menuItem, config] of Object.entries(settingsState.menuitems ?? {})) {
|
||||
if (config && !config.toggle) {
|
||||
css += `li[data-key=${menuItem}],section[data-key=${menuItem}]{display:var(--menuHidden) !important;transition:1s;}`;
|
||||
// Scope to the native SEQTA list only — the custom Svelte sidebar
|
||||
// filters visibility in JS and must not be forced hidden by this rule.
|
||||
css += `#menu > ul:not(#bsplus-sidebar-root) li[data-key=${menuItem}],#menu > ul:not(#bsplus-sidebar-root) section[data-key=${menuItem}]{display:var(--menuHidden) !important;transition:1s;}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
|
||||
import {
|
||||
ChangeMenuItemPositions,
|
||||
MenuOptionsOpen,
|
||||
} from "@/seqta/utils/Openers/OpenMenuOptions";
|
||||
import { ChangeMenuItemPositions } from "@/seqta/utils/Openers/menuOrder";
|
||||
import { isMenuOptionsOpen } from "@/seqta/utils/Openers/menuOptionsState";
|
||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
import stringToHTML from "@/seqta/utils/stringToHTML";
|
||||
import { waitForEngageMenuList } from "@/seqta/utils/waitForEngageMenuList";
|
||||
@@ -12,48 +10,6 @@ import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
||||
|
||||
const BETTERSEQTA_ICON_ATTR = "data-betterseqta-icon";
|
||||
|
||||
export function insertMenuItemAfterKey(
|
||||
menuList: HTMLElement,
|
||||
item: HTMLElement,
|
||||
afterKey: string,
|
||||
): void {
|
||||
const after = menuList.querySelector(
|
||||
`:scope > li[data-key="${afterKey}"], :scope > section[data-key="${afterKey}"]`,
|
||||
);
|
||||
if (after instanceof HTMLElement) {
|
||||
after.insertAdjacentElement("afterend", item);
|
||||
} else {
|
||||
menuList.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
export function insertKeyAfterInOrder(
|
||||
order: string[],
|
||||
key: string,
|
||||
afterKey: string,
|
||||
): string[] {
|
||||
if (order.includes(key)) return order;
|
||||
const copy = [...order];
|
||||
const afterIdx = copy.indexOf(afterKey);
|
||||
if (afterIdx >= 0) {
|
||||
copy.splice(afterIdx + 1, 0, key);
|
||||
} else {
|
||||
copy.push(key);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Default Analytics immediately below Courses in saved menu order. */
|
||||
export function ensureAnalyticsMenuOrder(): void {
|
||||
for (const key of ["defaultmenuorder", "menuorder"] as const) {
|
||||
const order = settingsState[key];
|
||||
if (key === "menuorder" && order.length === 0) continue;
|
||||
if (!order.includes("analytics")) {
|
||||
settingsState[key] = insertKeyAfterInOrder(order, "analytics", "courses");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getMenuLabel(element: HTMLElement): HTMLElement | null {
|
||||
const label = element.querySelector(":scope > label");
|
||||
return label instanceof HTMLElement ? label : null;
|
||||
@@ -107,7 +63,7 @@ export function replaceMenuSVG(element: HTMLElement, svg: string) {
|
||||
}
|
||||
|
||||
export function processMenuItemNode(node: HTMLElement) {
|
||||
if (!isTopLevelSidebarItem(node) || MenuOptionsOpen) return;
|
||||
if (!isTopLevelSidebarItem(node) || isMenuOptionsOpen()) return;
|
||||
|
||||
const key = node.dataset.key as keyof typeof MenuitemSVGKey | undefined;
|
||||
if (key && MenuitemSVGKey[key]) {
|
||||
@@ -119,7 +75,7 @@ export function processMenuItemNode(node: HTMLElement) {
|
||||
}
|
||||
|
||||
function processTopLevelMenuItems(reorder = !isSeqtaEngageExperience()) {
|
||||
if (MenuOptionsOpen) return;
|
||||
if (isMenuOptionsOpen()) return;
|
||||
|
||||
const topList = getTopLevelMenuList();
|
||||
if (!topList) return;
|
||||
@@ -186,7 +142,7 @@ export async function observeMenuItemPosition() {
|
||||
if (!isTopLevelSidebarItem(node)) return;
|
||||
if ((element as any)[processedSymbol]) return;
|
||||
|
||||
if (!MenuOptionsOpen) {
|
||||
if (!isMenuOptionsOpen()) {
|
||||
processMenuItemNode(node);
|
||||
ChangeMenuItemPositions(settingsState.menuorder);
|
||||
(element as any)[processedSymbol] = true;
|
||||
|
||||
Reference in New Issue
Block a user