Harden Lildra layout and refresh branding
This commit is contained in:
+7
-1
@@ -1,5 +1,7 @@
|
||||
# Packaged extension releases
|
||||
*.zip
|
||||
*.crx
|
||||
*.pem
|
||||
|
||||
# Operating system files
|
||||
.DS_Store
|
||||
@@ -12,4 +14,8 @@ Thumbs.db
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.log
|
||||
*.log
|
||||
|
||||
# Local browser test profiles
|
||||
.chrome-profile/
|
||||
.edge-profile/
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Lildra contributor instructions
|
||||
|
||||
## Purpose and product boundaries
|
||||
|
||||
Lildra is a deliberately small Chrome/Edge Manifest V3 extension for distraction-free reading on Canvas/QLearn course lesson and wiki pages. It hides Canvas navigation, keeps the native lesson content at a centred reading width, and adds only a title bar and version label.
|
||||
|
||||
Never move, clone, wrap, or reconstruct lesson DOM. Never restyle lesson typography, headings, images, links, tables, embeds, or user content. Keep selectors limited to known Canvas shell elements and guarded by strict page detection. Do not broaden activation to dashboards, assignments, discussions, quizzes, modules, account pages, or arbitrary websites.
|
||||
|
||||
Do not add frameworks, build tools, dependencies, analytics, telemetry, remote scripts, content delivery networks, settings pages, options pages, popup interfaces, accounts, storage, or unrelated features. Do not add persistent host permissions when `activeTab` is sufficient.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `manifest.json` defines the MV3 service worker, toolbar action, Alt+L command, permissions, and icons.
|
||||
- `background.js` handles the toolbar and command, then injects `lildra.js` into the active tab.
|
||||
- `lildra.js` validates the URL and Canvas DOM, injects scoped layout CSS and two inert UI elements, watches for dynamic title changes, and owns complete toggle cleanup.
|
||||
- `icons/` contains the packaged toolbar/store icons.
|
||||
|
||||
There is intentionally no content script registered for persistent execution. Re-injecting `lildra.js` toggles the current page off through its page-scoped state object.
|
||||
|
||||
## Coding standards
|
||||
|
||||
- Use dependency-free, modern JavaScript supported by current Chrome and Edge.
|
||||
- Keep strict mode enabled and avoid leaking globals except the single toggle state key.
|
||||
- Scope every CSS rule under `html.lildra-active` and target Canvas shell IDs/classes narrowly.
|
||||
- Treat injected visual labels as decorative when equivalent page information already exists, so screen readers do not receive duplicate landmarks or titles.
|
||||
- Read package metadata from `chrome.runtime.getManifest()`; do not duplicate the version in JavaScript or CSS.
|
||||
- Preserve cleanup symmetry: every observer, class, style, and element added on enable must be removed on disable.
|
||||
- Prefer clear names and comments that explain constraints, not obvious syntax.
|
||||
|
||||
## Test procedure
|
||||
|
||||
Before release:
|
||||
|
||||
1. Parse `manifest.json` and confirm every referenced JavaScript and icon file exists.
|
||||
2. Load the unpacked extension in current Chrome and Edge with no extension errors.
|
||||
3. On a QLearn lesson page, toggle on and off with both the toolbar button and Alt+L.
|
||||
4. Confirm the global and course navigation hide, lesson content remains centred at its normal bounded width, and no lesson content styling or DOM position changes.
|
||||
5. Test long and dynamically rendered titles; confirm centring, one-line ellipsis, and title updates.
|
||||
6. Test desktop and narrow/mobile-width viewports, including pages with tables, images, links, and embeds.
|
||||
7. Confirm `Lildra vX.Y.Z` matches `manifest.json` and remains unobtrusive.
|
||||
8. Try dashboards, module lists, assignments, discussions, quizzes, non-content page routes, HTTP pages, and unrelated sites; Lildra must not activate.
|
||||
9. Navigate between Canvas pages without a full reload and confirm no stale title or injected UI remains.
|
||||
10. Check keyboard-only use and a screen reader for duplicate announcements or lost access to lesson content.
|
||||
|
||||
## Versioning and releases
|
||||
|
||||
Use Semantic Versioning. Change the version only in `manifest.json`: patch for fixes, minor for backward-compatible behaviour, and major for incompatible product changes. Move completed notes from `Unreleased` into a dated version section in `CHANGELOG.md`. Never commit packaged ZIP files. Do not commit or push unless the user explicitly requests it.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to Lildra are documented here. Versions follow Semantic Versioning.
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Changed
|
||||
|
||||
- Restricted activation to HTTPS Canvas course page/wiki content routes with expected Canvas page structure.
|
||||
- Simplified layout overrides so Canvas content retains a bounded, responsive reading width.
|
||||
- Made the injected title follow dynamically rendered Canvas titles.
|
||||
- Read the displayed version from the extension manifest.
|
||||
- Improved injected UI accessibility and toggle cleanup.
|
||||
- Expanded project documentation and maintenance guidance.
|
||||
|
||||
## 0.2.3
|
||||
|
||||
- Added the compact title bar and bottom-right product label.
|
||||
- Added toolbar and Alt+L toggling.
|
||||
@@ -1,11 +1,32 @@
|
||||
# Lildra v0.2.3
|
||||
# Lildra
|
||||
|
||||
A minimal Canvas/QLearn viewing mode.
|
||||
Lildra is a small Chrome and Edge Manifest V3 extension that gives Canvas/QLearn lesson pages a distraction-free reading layout. It hides Canvas navigation, centres the normal-width lesson area, and adds a compact title bar without moving or reconstructing lesson content.
|
||||
|
||||
- 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
|
||||
## Behaviour
|
||||
|
||||
Toggle with the extension button or **Alt+L**.
|
||||
- Runs only on HTTPS Canvas course page/wiki URLs that contain Canvas lesson content.
|
||||
- Hides the global header and left course navigation.
|
||||
- Preserves Canvas lesson markup, typography, media, tables, links, and embeds.
|
||||
- Shows the current page title in a centred, single-line bar.
|
||||
- Updates the title when Canvas renders it dynamically.
|
||||
- Shows the extension version in the bottom-right corner.
|
||||
- Toggles from the toolbar button or **Alt+L**.
|
||||
|
||||
Lildra requests only `activeTab` and `scripting`. It has no analytics, remote code, settings, popup, or persistent site access.
|
||||
|
||||
## Install locally
|
||||
|
||||
1. Open `chrome://extensions` in Chrome or `edge://extensions` in Edge.
|
||||
2. Enable **Developer mode**.
|
||||
3. Choose **Load unpacked** and select this repository directory.
|
||||
4. Open a Canvas/QLearn lesson page and select the Lildra toolbar button or press **Alt+L**.
|
||||
|
||||
The browser may reserve or override shortcuts. Check the extension shortcut page if **Alt+L** does not work.
|
||||
|
||||
## Test
|
||||
|
||||
Follow the manual checklist in [AGENTS.md](AGENTS.md). There is no build step and there are no dependencies.
|
||||
|
||||
## Privacy and security
|
||||
|
||||
Lildra runs only after an explicit toolbar click or keyboard command. It does not collect, store, or transmit data. The package contains no remote scripts.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -1,41 +1,47 @@
|
||||
(() => {
|
||||
"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";
|
||||
const STYLE_ID = "lildra-style";
|
||||
const TITLE_BAR_ID = "lildra-title-bar";
|
||||
const TITLE_ID = "lildra-title";
|
||||
const VERSION_LABEL_ID = "lildra-version-label";
|
||||
const STATE_KEY = "__lildraState";
|
||||
|
||||
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();
|
||||
const existingState = window[STATE_KEY];
|
||||
if (existingState) {
|
||||
existingState.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
function isSupportedRoute() {
|
||||
return location.protocol === "https:" &&
|
||||
/^\/courses\/[^/]+\/(?:pages|wiki)\/[^/]+\/?$/.test(location.pathname);
|
||||
}
|
||||
|
||||
function isCanvasContentPage() {
|
||||
return isSupportedRoute() && Boolean(
|
||||
document.querySelector("#application #wrapper") &&
|
||||
document.querySelector("#wiki_page_show")
|
||||
);
|
||||
}
|
||||
|
||||
if (!isCanvasContentPage()) 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 selectors = [
|
||||
"#breadcrumbs [aria-current='page'] .ellipsible",
|
||||
"#breadcrumbs [aria-current='page']",
|
||||
"#wiki_page_show .page-title",
|
||||
"#content h1.page-title",
|
||||
"#mobile-header .mobile-header-title div:last-child"
|
||||
];
|
||||
|
||||
const found = candidates
|
||||
.map((value) => value?.trim())
|
||||
.find((value) => value && value.length > 0);
|
||||
for (const selector of selectors) {
|
||||
const title = document.querySelector(selector)?.textContent?.trim();
|
||||
if (title) return title;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -44,7 +50,6 @@
|
||||
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,
|
||||
@@ -53,84 +58,65 @@
|
||||
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} #wrapper,
|
||||
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} #wrapper {
|
||||
box-sizing: border-box !important;
|
||||
padding-top: 42px !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;
|
||||
box-sizing: border-box !important;
|
||||
width: calc(100% - 32px) !important;
|
||||
max-width: 1100px !important;
|
||||
margin: 0 auto !important;
|
||||
float: none !important;
|
||||
margin-right: auto !important;
|
||||
margin-left: auto !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} {
|
||||
html.${ROOT_CLASS} #${TITLE_BAR_ID} {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
left: 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;
|
||||
height: 42px !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;
|
||||
background: #fff !important;
|
||||
border-bottom: 1px solid rgb(0 0 0 / 14%) !important;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 7%) !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;
|
||||
font: 700 16px/1 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
||||
}
|
||||
|
||||
html.${ROOT_CLASS} #${FOOTER_ID} {
|
||||
html.${ROOT_CLASS} #${TITLE_ID} {
|
||||
min-width: 0 !important;
|
||||
overflow: hidden !important;
|
||||
text-align: center !important;
|
||||
text-overflow: ellipsis !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
html.${ROOT_CLASS} #${VERSION_LABEL_ID} {
|
||||
position: fixed !important;
|
||||
right: 14px !important;
|
||||
bottom: 9px !important;
|
||||
right: 10px !important;
|
||||
bottom: 8px !important;
|
||||
z-index: 2147483646 !important;
|
||||
padding: 4px 7px !important;
|
||||
padding: 3px 6px !important;
|
||||
border-radius: 4px !important;
|
||||
background: rgba(255, 255, 255, 0.9) !important;
|
||||
background: rgb(255 255 255 / 88%) !important;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 10%) !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;
|
||||
font: 700 11px/1 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
||||
pointer-events: none !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
@@ -138,25 +124,72 @@
|
||||
padding-top: 38px !important;
|
||||
}
|
||||
|
||||
html.${ROOT_CLASS} #${HEADER_ID} {
|
||||
html.${ROOT_CLASS} #main {
|
||||
width: calc(100% - 20px) !important;
|
||||
}
|
||||
|
||||
html.${ROOT_CLASS} #${TITLE_BAR_ID} {
|
||||
height: 38px !important;
|
||||
padding: 0 12px !important;
|
||||
padding: 0 10px !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.id = HEADER_ID;
|
||||
header.setAttribute("role", "banner");
|
||||
header.textContent = getPageTitle();
|
||||
const titleBar = document.createElement("div");
|
||||
titleBar.id = TITLE_BAR_ID;
|
||||
titleBar.setAttribute("aria-hidden", "true");
|
||||
|
||||
const footer = document.createElement("div");
|
||||
footer.id = FOOTER_ID;
|
||||
footer.textContent = `Lildra v${VERSION}`;
|
||||
const title = document.createElement("div");
|
||||
title.id = TITLE_ID;
|
||||
title.textContent = getPageTitle();
|
||||
titleBar.append(title);
|
||||
|
||||
document.documentElement.appendChild(style);
|
||||
document.body.appendChild(header);
|
||||
document.body.appendChild(footer);
|
||||
const versionLabel = document.createElement("div");
|
||||
versionLabel.id = VERSION_LABEL_ID;
|
||||
versionLabel.setAttribute("aria-hidden", "true");
|
||||
versionLabel.textContent = `Lildra v${chrome.runtime.getManifest().version}`;
|
||||
|
||||
let updatePending = false;
|
||||
let updateFrame = 0;
|
||||
const observer = new MutationObserver(() => {
|
||||
if (updatePending) return;
|
||||
updatePending = true;
|
||||
|
||||
updateFrame = requestAnimationFrame(() => {
|
||||
updateFrame = 0;
|
||||
updatePending = false;
|
||||
if (!isSupportedRoute()) {
|
||||
remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const nextTitle = getPageTitle();
|
||||
if (nextTitle && title.textContent !== nextTitle) title.textContent = nextTitle;
|
||||
});
|
||||
});
|
||||
|
||||
function remove() {
|
||||
observer.disconnect();
|
||||
if (updateFrame) cancelAnimationFrame(updateFrame);
|
||||
document.documentElement.classList.remove(ROOT_CLASS);
|
||||
style.remove();
|
||||
titleBar.remove();
|
||||
versionLabel.remove();
|
||||
delete window[STATE_KEY];
|
||||
}
|
||||
|
||||
window[STATE_KEY] = { remove };
|
||||
document.documentElement.append(style);
|
||||
document.body.append(titleBar, versionLabel);
|
||||
document.documentElement.classList.add(ROOT_CLASS);
|
||||
observer.observe(document.querySelector("#application"), {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true
|
||||
});
|
||||
observer.observe(document.querySelector("head > title"), {
|
||||
childList: true,
|
||||
characterData: true
|
||||
});
|
||||
})();
|
||||
|
||||
+13
-7
@@ -2,7 +2,7 @@
|
||||
"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.",
|
||||
"description": "A distraction-free reading layout for Canvas and QLearn lesson pages.",
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
"scripting"
|
||||
@@ -11,7 +11,13 @@
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"action": {
|
||||
"default_title": "Toggle Lildra"
|
||||
"default_title": "Toggle Lildra",
|
||||
"default_icon": {
|
||||
"16": "icons/LildraLogo16.png",
|
||||
"32": "icons/LildraLogo32.png",
|
||||
"48": "icons/LildraLogo48.png",
|
||||
"128": "icons/LildraLogo128.png"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"toggle-lildra": {
|
||||
@@ -23,9 +29,9 @@
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"16": "icons/icon16.png",
|
||||
"32": "icons/icon32.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
"16": "icons/LildraLogo16.png",
|
||||
"32": "icons/LildraLogo32.png",
|
||||
"48": "icons/LildraLogo48.png",
|
||||
"128": "icons/LildraLogo128.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user