commit e6e5336209126f97d5e919305ae423b4c01d02db Author: Kaden Napper Date: Mon Jul 27 18:33:38 2026 +1000 feat: initialise Lildra browser extension diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4aa6a69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Packaged extension releases +*.zip + +# Operating system files +.DS_Store +Thumbs.db + +# Editor files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json + +# Temporary files +*.tmp +*.log \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee4d395 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Lildra v0.2.3 + +A minimal Canvas/QLearn viewing mode. + +- Hides the global top navigation and course navigation +- Keeps the native Canvas page content unchanged +- Centres the normal-width content area +- Shows a compact centred single-line page title with ellipsis +- Shows the product name and version at the bottom-right + +Toggle with the extension button or **Alt+L**. diff --git a/background.js b/background.js new file mode 100644 index 0000000..d90494f --- /dev/null +++ b/background.js @@ -0,0 +1,21 @@ +"use strict"; + +async function toggleLildra(tab) { + if (!tab?.id) return; + + try { + await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + files: ["lildra.js"] + }); + } catch (error) { + console.warn("Lildra could not run on this page:", error); + } +} + +chrome.action.onClicked.addListener(toggleLildra); +chrome.commands.onCommand.addListener(async (command) => { + if (command !== "toggle-lildra") return; + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + await toggleLildra(tab); +}); diff --git a/icons/icon128.png b/icons/icon128.png new file mode 100644 index 0000000..29f48e5 Binary files /dev/null and b/icons/icon128.png differ diff --git a/icons/icon16.png b/icons/icon16.png new file mode 100644 index 0000000..5105f35 Binary files /dev/null and b/icons/icon16.png differ diff --git a/icons/icon32.png b/icons/icon32.png new file mode 100644 index 0000000..c70978f Binary files /dev/null and b/icons/icon32.png differ diff --git a/icons/icon48.png b/icons/icon48.png new file mode 100644 index 0000000..681a8a0 Binary files /dev/null and b/icons/icon48.png differ diff --git a/lildra.js b/lildra.js new file mode 100644 index 0000000..777f78e --- /dev/null +++ b/lildra.js @@ -0,0 +1,162 @@ +(() => { + "use strict"; + + const VERSION = "0.2.3"; + const ROOT_CLASS = "lildra-active"; + const STYLE_ID = "lildra-layout-style"; + const HEADER_ID = "lildra-page-header"; + const FOOTER_ID = "lildra-product-footer"; + + function removeLildra() { + document.documentElement.classList.remove(ROOT_CLASS); + document.getElementById(STYLE_ID)?.remove(); + document.getElementById(HEADER_ID)?.remove(); + document.getElementById(FOOTER_ID)?.remove(); + } + + // Toggle off when run a second time. + if (document.documentElement.classList.contains(ROOT_CLASS)) { + removeLildra(); + return; + } + + function getPageTitle() { + const candidates = [ + document.querySelector("#breadcrumbs [aria-current='page'] .ellipsible")?.textContent, + document.querySelector("#breadcrumbs [aria-current='page']")?.textContent, + document.querySelector(".mobile-header-title div:last-child")?.textContent, + document.querySelector("#wiki_page_show .page-title")?.textContent, + document.querySelector("#content h1.page-title")?.textContent + ]; + + const found = candidates + .map((value) => value?.trim()) + .find((value) => value && value.length > 0); + + if (found) return found; + + // Canvas document titles normally end with ": Course name". + const browserTitle = document.title.trim(); + const separator = browserTitle.lastIndexOf(":"); + return separator > 0 ? browserTitle.slice(0, separator).trim() : browserTitle; + } + + const style = document.createElement("style"); + style.id = STYLE_ID; + style.textContent = ` + /* Hide Canvas/QLearn navigation only. */ + html.${ROOT_CLASS} #header, + html.${ROOT_CLASS} #mobile-header, + html.${ROOT_CLASS} #mobileContextNavContainer, + html.${ROOT_CLASS} .ic-app-nav-toggle-and-crumbs, + html.${ROOT_CLASS} #left-side { + display: none !important; + } + + /* Remove navigation offsets while keeping a normal, centred reading width. */ + html.${ROOT_CLASS} #wrapper { + margin-left: 0 !important; + padding-top: 42px !important; + } + + html.${ROOT_CLASS} body.primary-nav-expanded #wrapper, + html.${ROOT_CLASS} body.course-menu-expanded #wrapper, + html.${ROOT_CLASS} body.with-left-side #wrapper { + margin-left: 0 !important; + } + + html.${ROOT_CLASS} #main { + display: block !important; + width: 100% !important; + max-width: none !important; + margin: 0 !important; + } + + html.${ROOT_CLASS} #not_right_side { + display: block !important; + width: min(1100px, calc(100% - 32px)) !important; + max-width: 1100px !important; + margin: 0 auto !important; + float: none !important; + } + + html.${ROOT_CLASS} #content-wrapper, + html.${ROOT_CLASS} #content { + width: 100% !important; + max-width: none !important; + margin-left: 0 !important; + margin-right: 0 !important; + } + + /* Small fixed title bar. */ + html.${ROOT_CLASS} #${HEADER_ID} { + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + z-index: 2147483646 !important; + box-sizing: border-box !important; + height: 42px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + padding: 0 18px !important; + margin: 0 !important; + overflow: hidden !important; + white-space: nowrap !important; + text-overflow: ellipsis !important; + text-align: center !important; + background: #ffffff !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.14) !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07) !important; + color: #1d2733 !important; + font-family: Lato, "Helvetica Neue", Helvetica, Arial, sans-serif !important; + font-size: 16px !important; + font-weight: 700 !important; + line-height: 1 !important; + } + + html.${ROOT_CLASS} #${FOOTER_ID} { + position: fixed !important; + right: 14px !important; + bottom: 9px !important; + z-index: 2147483646 !important; + padding: 4px 7px !important; + border-radius: 4px !important; + background: rgba(255, 255, 255, 0.9) !important; + color: #5f259f !important; + font-family: Lato, "Helvetica Neue", Helvetica, Arial, sans-serif !important; + font-size: 12px !important; + font-weight: 700 !important; + line-height: 1 !important; + pointer-events: none !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important; + } + + @media (max-width: 700px) { + html.${ROOT_CLASS} #wrapper { + padding-top: 38px !important; + } + + html.${ROOT_CLASS} #${HEADER_ID} { + height: 38px !important; + padding: 0 12px !important; + font-size: 14px !important; + } + } + `; + + const header = document.createElement("div"); + header.id = HEADER_ID; + header.setAttribute("role", "banner"); + header.textContent = getPageTitle(); + + const footer = document.createElement("div"); + footer.id = FOOTER_ID; + footer.textContent = `Lildra v${VERSION}`; + + document.documentElement.appendChild(style); + document.body.appendChild(header); + document.body.appendChild(footer); + document.documentElement.classList.add(ROOT_CLASS); +})(); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..34bb526 --- /dev/null +++ b/manifest.json @@ -0,0 +1,31 @@ +{ + "manifest_version": 3, + "name": "Lildra", + "version": "0.2.3", + "description": "A minimal Canvas and QLearn page view with hidden navigation, centred content, a compact fixed page title and version label.", + "permissions": [ + "activeTab", + "scripting" + ], + "background": { + "service_worker": "background.js" + }, + "action": { + "default_title": "Toggle Lildra" + }, + "commands": { + "toggle-lildra": { + "suggested_key": { + "default": "Alt+L", + "mac": "Alt+L" + }, + "description": "Toggle Lildra" + } + }, + "icons": { + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } +} \ No newline at end of file