diff --git a/CHANGELOG.md b/CHANGELOG.md index 45e2ec4..63df109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +# [1.3.0](https://github.com/skid-dev/UltraBox/compare/v1.2.1...v1.3.0) (2026-05-05) + + +### Bug Fixes + +* Remove inject for pulling RSS feed ([faa111f](https://github.com/skid-dev/UltraBox/commit/faa111fad47880e55772d536b82674ebada0c48a)) + + +### Features + +* Add option to change width of launcher ([2fa2492](https://github.com/skid-dev/UltraBox/commit/2fa249247432beff53e1877a5f37fcbe99e13b4c)) +* Keyboard navigation for news search autocomplete ([b1e7efa](https://github.com/skid-dev/UltraBox/commit/b1e7efadcbfcbf31ea509042b601e18843df8676)) +* Reduce content width module ([1736083](https://github.com/skid-dev/UltraBox/commit/1736083b3d055bfef144f3d1d2cb20f67146b4b4)) + +## [1.2.1](https://github.com/skid-dev/UltraBox/compare/v1.2.0...v1.2.1) (2026-05-04) + + +### Bug Fixes + +* Fix memory issues with large diffs ([e296a8e](https://github.com/skid-dev/UltraBox/commit/e296a8e14f1d96b6d397b32cb6d027bbb8897f4b)) + +# [1.2.0](https://github.com/skid-dev/UltraBox/compare/v1.1.1...v1.2.0) (2026-04-30) + + +### Bug Fixes + +* Fix improper styling of timeline dots in Schooltape Compatibility. ([16467a8](https://github.com/skid-dev/UltraBox/commit/16467a8fb83ee592db08be03f9e10b092919b1f4)) +* Fix launcher scrolling to bottom of page ([e5f52d8](https://github.com/skid-dev/UltraBox/commit/e5f52d87fd23d0fa466f2d21c21854a3f9cd1fbb)) +* Schooltape styles for autocomplete ([#14](https://github.com/skid-dev/UltraBox/issues/14)) ([20b6886](https://github.com/skid-dev/UltraBox/commit/20b6886e79d247d25def27c835e98fcf9955145b)) + + +### Features + +* Schooltape detection ([ef94564](https://github.com/skid-dev/UltraBox/commit/ef945645b7dbf567fa85aadce2cbe7daa8222b65)) +* some minor improvements ([b988dfd](https://github.com/skid-dev/UltraBox/commit/b988dfd1eac6bbf121d0834641bee5c8052ad7f2)) + ## [1.1.1](https://github.com/skid-dev/UltraBox/compare/v1.1.0...v1.1.1) (2026-04-02) diff --git a/README.md b/README.md index a489752..3b109b7 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,13 @@ A Chrome extension for the SchoolBox LMS with integrations for the Box of Books ## Features -### **Schooltape users: Please enable __Schooltape compatibility__ in the extension settings to ensure proper styling of elements created by modules in this extension!** -> This is a temporary workaround while automatic Schooltape detection is still work in progress. - - Launcher: A search bar for quick access to news articles and classes, as well as textbooks from Box of Books. - - Search through book headings in Box of Books. + - Search through book headings in Box of Books. - Dark theme: Dark theme for the SchoolBox website and its pages (still a work in progress) - News Chips: Replaces boring SchoolBox tabs in the news section with much more elegant chips and a search bar. - Announcement / Post / News Item Edit history. - Automatic detection for the news headlines RSS feed. - ### In progress - Frequently viewed pages: quick access for pages and resources you commonly access, separated by your class periods. @@ -22,16 +18,18 @@ A Chrome extension for the SchoolBox LMS with integrations for the Box of Books ## Manual installation (via pre-built package) ### For chrome and chromium based browsers - 1. Download the latest release (or a specific version you wish to install) - 1. Unzip the file - 1. Open your browsers extensions page (For chrome it's chrome://extensions) - 1. If not already enabled, click the toggle in the top to enable developer mode. - 1. A new ribbon should appear underneath the top navigation bar. Click the "Load Unpacked" button. - 1. Select the folder that was extracted earlier. + +1. Download the latest release (or a specific version you wish to install) +1. Unzip the file +1. Open your browsers extensions page (For chrome it's chrome://extensions) +1. If not already enabled, click the toggle in the top to enable developer mode. +1. A new ribbon should appear underneath the top navigation bar. Click the "Load Unpacked" button. +1. Select the folder that was extracted earlier. ## Development Install dependencies using Bun: + ```bash # install dependencies bun install @@ -39,12 +37,15 @@ bun install # build bun run build ``` + For development mode with file watching: + ```bash bun run dev ``` Load the `dist/` directory as an unpacked extension in Chrome to test. + 1. Open Google Chrome and go to `chrome://extensions/`. 2. Enable the Developer Mode toggle in the top right. 3. Click on "load unpacked" in the toolbar that appears. diff --git a/bun.lock b/bun.lock index 2b08466..38e8d78 100644 --- a/bun.lock +++ b/bun.lock @@ -10,10 +10,12 @@ "@babel/preset-react": "^7.27.1", "@babel/preset-typescript": "^7.27.1", "@types/chrome": "^0.1.38", + "@types/diff": "^8.0.0", "@types/html-to-text": "^9.0.4", "babel-loader": "^10.0.0", "copy-webpack-plugin": "^13.0.0", "css-loader": "^7.1.2", + "diff": "^9.0.0", "fast-xml-parser": "^5.2.3", "fuse.js": "^7.1.0", "html-to-text": "^9.0.5", @@ -315,6 +317,8 @@ "@types/chrome": ["@types/chrome@0.1.38", "", { "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" } }, "sha512-5aK4m9wZqoWAoB98aElESLm/5pXpqJnFWMNoiCs/XdPsXR6wNdVkJFSdQ9Wr4PnTuUrxD0SuNuDHh3EG5QeBzA=="], + "@types/diff": ["@types/diff@8.0.0", "", { "dependencies": { "diff": "*" } }, "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw=="], + "@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="], "@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="], @@ -505,6 +509,8 @@ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + "diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], "dom-converter": ["dom-converter@0.2.0", "", { "dependencies": { "utila": "~0.4" } }, "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA=="], diff --git a/package.json b/package.json index c7debb8..1d6f717 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "schoolbox-chrome-extension", - "version": "1.1.1", + "version": "1.3.0", "description": "A Chrome extension for SchoolBox built with TypeScript, React, and Bun.", "main": "src/background/background.ts", "scripts": { @@ -25,10 +25,12 @@ "@babel/preset-react": "^7.27.1", "@babel/preset-typescript": "^7.27.1", "@types/chrome": "^0.1.38", + "@types/diff": "^8.0.0", "@types/html-to-text": "^9.0.4", "babel-loader": "^10.0.0", "copy-webpack-plugin": "^13.0.0", "css-loader": "^7.1.2", + "diff": "^9.0.0", "fast-xml-parser": "^5.2.3", "fuse.js": "^7.1.0", "html-to-text": "^9.0.5", diff --git a/src/background/events/injects/display_history.ts b/src/background/events/injects/display_history.ts index 0f30a97..31a7e5f 100644 --- a/src/background/events/injects/display_history.ts +++ b/src/background/events/injects/display_history.ts @@ -1,28 +1,28 @@ -import { Module } from "../../../types/module"; +import { Module } from "../../../types/module" -export default { +export default { setting: s => { return !!s.recents_list_module }, condition: async (base, settings, helper_fns) => { - return (await helper_fns.url_begins_with("/news")) + return await helper_fns.url_begins_with("/news") }, action: async (base, settings, helper_fns) => { await chrome.scripting.executeScript({ - target: {tabId: base.tab_id}, - files: ["display_history.js"] + target: { tabId: base.tab_id }, + files: ["display_history.js"], }) await chrome.scripting.insertCSS({ - target: {tabId: base.tab_id}, - files: ["news_history.css"] + target: { tabId: base.tab_id }, + files: ["news_history.css"], }) if (settings.schooltape_compatibility) { await chrome.scripting.insertCSS({ - target: {tabId: base.tab_id}, - files: ["schooltape/post_history_styles.css"] + target: { tabId: base.tab_id }, + files: ["schooltape/post_history_styles.css"], }) } - } -} \ No newline at end of file + }, +} diff --git a/src/background/events/injects/news_tracker.ts b/src/background/events/injects/news_tracker.ts index 9816472..bcc514e 100644 --- a/src/background/events/injects/news_tracker.ts +++ b/src/background/events/injects/news_tracker.ts @@ -1,13 +1,11 @@ import { Module } from "../../../types/module" - +import { poll_feed } from "../../pull_feed" + export default { setting: s => { return !!s.record_post_history }, - action: async (base) => { - await chrome.scripting.executeScript({ - target: { tabId: base.tab_id }, - files: ["history_puller.js"], - }) + action: async () => { + await poll_feed() }, } diff --git a/src/background/events/injects/reduce_width.ts b/src/background/events/injects/reduce_width.ts new file mode 100644 index 0000000..b8ee646 --- /dev/null +++ b/src/background/events/injects/reduce_width.ts @@ -0,0 +1,16 @@ +import { Module } from "../../../types/module" + +export default { + setting: s => { + return s.reduce_content_width + }, + condition: (_base, _settings, helper_fns) => { + return helper_fns.is_schoolbox_page + }, + action: async base => { + await chrome.scripting.insertCSS({ + target: { tabId: base.tab_id }, + files: ["reduce_width.css"], + }) + }, +} diff --git a/src/background/events/on_update.ts b/src/background/events/on_update.ts index 7e8e383..2f691f0 100644 --- a/src/background/events/on_update.ts +++ b/src/background/events/on_update.ts @@ -1,4 +1,9 @@ -import { check_in_schoolbox_domain, get_stored_settings, is_page, url_begins_with } from "../functions/utls/is_page" +import { + check_in_schoolbox_domain, + get_stored_settings, + is_page, + url_begins_with, +} from "../functions/utls/is_page" import track_page_if_in_domain from "../functions/utls/track_page" import dark_theme_css from "./injects/dark_theme_css" @@ -9,6 +14,7 @@ import launcher_shortcut from "./injects/launcher_shortcut" import news_tracker_fetch from "./injects/news_tracker" import news_tracker_display from "./injects/display_history" import detect_schooltape from "./injects/st_detect_inject" +import reduce_width from "./injects/reduce_width" const INJECTS = [ dark_theme_css, @@ -18,7 +24,8 @@ const INJECTS = [ launcher_shortcut, news_tracker_fetch, news_tracker_display, - detect_schooltape + detect_schooltape, + reduce_width, ] export default async function on_update(tab_id: number, _: any, tab: chrome.tabs.Tab) { diff --git a/src/background/functions/calculate_rev_metrics.ts b/src/background/functions/calculate_rev_metrics.ts index d919ac6..907e6b2 100644 --- a/src/background/functions/calculate_rev_metrics.ts +++ b/src/background/functions/calculate_rev_metrics.ts @@ -1,85 +1,24 @@ import { RevisionData } from "../../types/rev_history" +import { diffChars } from "diff" export async function calculate_new_metrics(old_data: RevisionData, new_data: RevisionData) { - const old_title = old_data.title || "" - const new_title = new_data.title || "" + let added_chars = 0 + let removed_chars = 0 + let unchanged_chars = 0 - let new_lines = 0 - let modified_lines = 0 - let deleted_lines = 0 + const changes = diffChars(old_data.content.toLowerCase(), new_data.content.toLowerCase()) - if (old_title != new_title) { - modified_lines++ - } - - // split into individual chars - const old_text = [...old_data.content.toLowerCase()] - const new_text = [...new_data.content.toLowerCase()] - - const m = old_text.length - const n = new_text.length - - // create a DP table of size (m+1) x (n+1) - // dp[i][j] represents the edit distance between old_text[0..i-1] and new_text[0..j-1] - const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)) - - // base cases - for (let i = 0; i <= m; i++) { - dp[i][0] = i // deleting all lines from old_text - } - for (let j = 0; j <= n; j++) { - dp[0][j] = j // adding all lines from new_text - } - - // build the DP table - for (let i = 1; i <= m; i++) { - for (let j = 1; j <= n; j++) { - if (old_text[i - 1] === new_text[j - 1]) { - dp[i][j] = dp[i - 1][j - 1] // no change - } else { - dp[i][j] = Math.min( - dp[i - 1][j] + 1, // delete old line - dp[i][j - 1] + 1, // add new line - dp[i - 1][j - 1] + 1 // modify line - ) - } - } - } - - // backtrack to count the number of new, modified, and deleted lines - let i = m - let j = n - while (i > 0 && j > 0) { - if (old_text[i - 1] === new_text[j - 1]) { - // if lines are the same, move diagonally - i-- - j-- - } else if (dp[i][j] === dp[i - 1][j] + 1) { - // line deleted from old_text - deleted_lines++ - i-- - } else if (dp[i][j] === dp[i][j - 1] + 1) { - // line added in new_text - new_lines++ - j-- + for (const part of changes) { + if (part.added) { + added_chars += part.count || 0 + } else if (part.removed) { + removed_chars += part.count || 0 } else { - modified_lines++ - i-- - j-- + unchanged_chars += part.count || 0 } } - // if there are remaining lines in old_text, they are deleted - while (i > 0) { - deleted_lines++ - i-- - } - - // if there are remaining lines in new_text, they are added - while (j > 0) { - new_lines++ - j-- - } - - return { new_lines, modified_lines, deleted_lines } + // A "modification" at the character level isn't a standard metric, + // it's usually just represented as a removal followed by an addition. + return { added_chars, removed_chars, unchanged_chars } } diff --git a/src/background/pull_feed.ts b/src/background/pull_feed.ts index 5696404..f1eca90 100644 --- a/src/background/pull_feed.ts +++ b/src/background/pull_feed.ts @@ -65,7 +65,7 @@ async function append_revision( } const now = Date.now() - const { new_lines, modified_lines, deleted_lines } = await calculate_new_metrics( + const { added_chars, removed_chars, unchanged_chars } = await calculate_new_metrics( prev_data_obj, rev_object ) @@ -74,9 +74,9 @@ async function append_revision( guid, prev_data_obj, now, - new_lines, - modified_lines, - deleted_lines + added_chars, + removed_chars, + unchanged_chars ) return data.rev_id diff --git a/src/background/set_default_settings.ts b/src/background/set_default_settings.ts index 25f0a95..c60328f 100644 --- a/src/background/set_default_settings.ts +++ b/src/background/set_default_settings.ts @@ -20,7 +20,10 @@ export async function init_settings(): Promise { // modules launcher_module: true, launcher_module_shortcut: true, + reduce_timetable_width: false, + news_search_module: true, + recents_list_module: true, record_post_history: true, record_setting_active: false, @@ -30,7 +33,10 @@ export async function init_settings(): Promise { await poll_feed() } -export async function set_setting(key: K, value: Settings[K]): Promise { +export async function set_setting( + key: K, + value: Settings[K] +): Promise { const current_settings = await chrome.storage.sync.get("settings") if (!current_settings.settings) { console.error("Settings not initialized yet.") diff --git a/src/content/modules/launcher/launch_homepage.ts b/src/content/modules/launcher/launch_homepage.ts index ef81153..1ea7fdf 100644 --- a/src/content/modules/launcher/launch_homepage.ts +++ b/src/content/modules/launcher/launch_homepage.ts @@ -1,14 +1,22 @@ +import { get_stored_settings } from "../../../background/functions/utls/is_page" import { poll_feed } from "../../../background/pull_feed" import { store_classes } from "./getters/get_classes" import { get_news_channels } from "./main" import setup_launcher from "./setup_launcher" // Function to inject the launcher -function inject_launcher(): void { - // Check if launcher already exists - if (document.getElementById("schoolbox-launcher")) { +async function inject_launcher(): Promise { + if (document.getElementById("ultrabox-launcher-injected-marker")) { + // launcher already injected, no need to inject again return } + + const marker = document.createElement("div") + marker.id = "ultrabox-launcher-injected-marker" + document.body.appendChild(marker) + + // Check if launcher already exists + const settings = await get_stored_settings() get_news_channels() poll_feed() @@ -24,6 +32,11 @@ function inject_launcher(): void { launcher_content_div.id = "schoolbox-launcher-content-div" launcher_content_div.className = "schoolbox-launcher-content-div" launcher_div.appendChild(launcher_content_div) + launcher_div.style.marginTop = "40px" + + if (settings?.reduce_timetable_width) { + launcher_div.style.width = "min(900px, 90%)" + } // move elements on the main page to this new launcher div // greeting heading diff --git a/src/content/modules/launcher/launcher_styles.css b/src/content/modules/launcher/launcher_styles.css index 6c43822..38e54f4 100644 --- a/src/content/modules/launcher/launcher_styles.css +++ b/src/content/modules/launcher/launcher_styles.css @@ -66,17 +66,6 @@ color: inherit; } -#schoolbox-launcher-background-div { - display: flex; - justify-content: center; - align-items: flex-start; -} - -#schoolbox-launcher-content-div { - width: min(900px, 90%); - margin-top: 40px; -} - .Component_Dashboard_GreetingController h1 { margin: 0; } diff --git a/src/content/modules/news_search/news_search.css b/src/content/modules/news_search/news_search.css index 13262c4..a1060f6 100644 --- a/src/content/modules/news_search/news_search.css +++ b/src/content/modules/news_search/news_search.css @@ -7,6 +7,9 @@ } #ultrabox-news-category-display { + display: inline-flex; + flex-shrink: 0; + align-items: center; font-weight: bold; color: white; background-color: #4287f5; @@ -14,10 +17,12 @@ border-radius: 10px; margin: 0 5px; font-size: initial; + white-space: nowrap; } #ultrabox-news-search { flex-grow: 1; + min-width: 0; margin: 0; } @@ -32,11 +37,22 @@ } .ultrabox-news-search-autocomplete-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; padding: 6px 14px; font-size: 0.95rem; color: inherit; cursor: pointer; transition: background-color 0.15s ease, color 0.15s ease; + white-space: nowrap; +} + +.ultrabox-news-search-autocomplete-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; } .ultrabox-news-search-autocomplete-item:nth-child(1) { @@ -53,7 +69,22 @@ border-top: 1px solid rgba(255, 255, 255, 0.08); } -.ultrabox-news-search-autocomplete-item:hover { +.ultrabox-news-search-autocomplete-item:hover, +.ultrabox-news-search-autocomplete-item.ultrabox-news-search-autocomplete-item-active { background-color: rgba(78, 161, 255, 0.15); color: #fff; } + +.ultrabox-news-search-autocomplete-hint { + display: none; + flex-shrink: 0; + color: rgba(255, 255, 255, 0.72); + font-size: 0.78rem; + font-weight: 600; +} + +#ultrabox-news-search-autocomplete.ultrabox-news-search-autocomplete-keyboard-active + .ultrabox-news-search-autocomplete-item-active + .ultrabox-news-search-autocomplete-hint { + display: inline; +} diff --git a/src/content/modules/news_search/news_search.ts b/src/content/modules/news_search/news_search.ts index 7ed283d..c4d40ae 100644 --- a/src/content/modules/news_search/news_search.ts +++ b/src/content/modules/news_search/news_search.ts @@ -14,24 +14,170 @@ function collect_news_categories(): void { } function collect_news_items(): void { - news_items = Array.from( - document.querySelectorAll(".information-list .actions-small-1") - ).map(elem => { - return { - elem: elem as HTMLLIElement, - category_string: elem.querySelector(`a[href^="/news?topic"]`)?.textContent?.trim() ?? null, + news_items = Array.from(document.querySelectorAll(".information-list .actions-small-1")).map( + elem => { + return { + elem: elem as HTMLLIElement, + category_string: + elem.querySelector(`a[href^="/news?topic"]`)?.textContent?.trim() ?? null, + } } - }) + ) } collect_news_categories() let current_category = "All" +let active_category_index = -1 +let is_keyboard_navigation_active = false const news_search_autocomplete = document.createElement("div") news_search_autocomplete.id = "ultrabox-news-search-autocomplete" +news_search_autocomplete.setAttribute("role", "listbox") document.body.appendChild(news_search_autocomplete) +function get_matching_categories(search_term: string): string[] { + return news_tabs_categories.filter(cat => cat.toLowerCase().includes(search_term)) +} + +function get_autocomplete_items(): HTMLElement[] { + return Array.from( + news_search_autocomplete.getElementsByClassName("ultrabox-news-search-autocomplete-item") + ) as HTMLElement[] +} + +function set_keyboard_navigation_active(is_active: boolean): void { + is_keyboard_navigation_active = is_active + news_search_autocomplete.classList.toggle( + "ultrabox-news-search-autocomplete-keyboard-active", + is_keyboard_navigation_active + ) +} + +function clear_active_category(): void { + get_autocomplete_items().forEach(item => { + item.classList.remove("ultrabox-news-search-autocomplete-item-active") + item.removeAttribute("aria-selected") + }) + + active_category_index = -1 +} + +function set_active_category(index: number): boolean { + const items = get_autocomplete_items() + + if (items.length === 0) { + clear_active_category() + return false + } + + const normalized_index = ((index % items.length) + items.length) % items.length + + items.forEach((item, current_index) => { + if (current_index === normalized_index) { + item.classList.add("ultrabox-news-search-autocomplete-item-active") + item.setAttribute("aria-selected", "true") + } else { + item.classList.remove("ultrabox-news-search-autocomplete-item-active") + item.removeAttribute("aria-selected") + } + }) + + active_category_index = normalized_index + return true +} + +function move_active_category(delta: number): boolean { + const items = get_autocomplete_items() + + if (items.length === 0) { + clear_active_category() + return false + } + + set_keyboard_navigation_active(true) + + if (active_category_index === -1) { + return set_active_category(delta > 0 ? 0 : items.length - 1) + } + + return set_active_category(active_category_index + delta) +} + +function get_active_category(): string | null { + const items = get_autocomplete_items() + const active_item = + active_category_index >= 0 && active_category_index < items.length + ? items[active_category_index] + : null + + return active_item?.dataset.category ?? null +} + +function hide_category_dropdown(): void { + news_search_autocomplete.innerHTML = "" + news_search_autocomplete.style.display = "none" + set_keyboard_navigation_active(false) + clear_active_category() +} + +function position_category_dropdown(search_input: HTMLInputElement): void { + const rect = search_input.getBoundingClientRect() + news_search_autocomplete.style.position = "absolute" + news_search_autocomplete.style.left = `${rect.left}px` + news_search_autocomplete.style.top = `${rect.bottom + window.scrollY}px` +} + +function select_category( + category: string, + search_input: HTMLInputElement, + category_display: HTMLDivElement +): void { + category_display.innerText = category + current_category = category + search_input.value = "" + hide_category_dropdown() + update_results(category, null) +} + +function render_category_dropdown( + categories: string[], + search_input: HTMLInputElement, + category_display: HTMLDivElement +): void { + news_search_autocomplete.innerHTML = "" + set_keyboard_navigation_active(false) + clear_active_category() + + for (let [index, category] of categories.entries()) { + const category_elem = document.createElement("div") + category_elem.className = "ultrabox-news-search-autocomplete-item" + category_elem.id = `ultrabox-news-search-autocomplete-item-${index}` + category_elem.dataset.category = category + category_elem.setAttribute("role", "option") + + const category_label = document.createElement("span") + category_label.className = "ultrabox-news-search-autocomplete-label" + category_label.innerText = category + + const keyboard_hint = document.createElement("span") + keyboard_hint.className = "ultrabox-news-search-autocomplete-hint" + keyboard_hint.innerText = "[tab] to select" + + category_elem.append(category_label, keyboard_hint) + category_elem.addEventListener("click", () => { + select_category(category, search_input, category_display) + }) + news_search_autocomplete.appendChild(category_elem) + } + + news_search_autocomplete.style.display = categories.length > 0 ? "block" : "none" + + if (categories.length > 0) { + position_category_dropdown(search_input) + } +} + function set_news_tabs_html() { if (!news_tabs_wrapper) { return @@ -45,6 +191,8 @@ function set_news_tabs_html() { ` const search_input = document.getElementById("ultrabox-news-search") as HTMLInputElement + search_input.setAttribute("aria-controls", "ultrabox-news-search-autocomplete") + search_input.setAttribute("aria-autocomplete", "list") const category_display = document.getElementById( "ultrabox-news-category-display" ) as HTMLDivElement @@ -53,59 +201,50 @@ function set_news_tabs_html() { const search_term = (e.target as HTMLInputElement).value.toLowerCase() if (search_term.length === 0) { - news_search_autocomplete.innerHTML = "" - news_search_autocomplete.style.display = "none" + hide_category_dropdown() update_results(current_category, null) return } - const available_categories = news_tabs_categories.filter(cat => - cat.toLowerCase().includes(search_term) - ) - - news_search_autocomplete.innerHTML = "" - for (let category of available_categories) { - const category_elem = document.createElement("div") - category_elem.className = "ultrabox-news-search-autocomplete-item" - category_elem.innerText = category - category_elem.addEventListener("click", () => { - category_display.innerText = category - current_category = category - search_input.value = "" - news_search_autocomplete.innerHTML = "" - news_search_autocomplete.style.display = "none" - update_results(category, null) - }) - news_search_autocomplete.appendChild(category_elem) - } - news_search_autocomplete.style.display = available_categories.length > 0 ? "block" : "none" - - // move the autocomplete div below the search input - const rect = search_input.getBoundingClientRect() - news_search_autocomplete.style.position = "absolute" - news_search_autocomplete.style.left = `${rect.left}px` - news_search_autocomplete.style.top = `${rect.bottom + window.scrollY}px` + const available_categories = get_matching_categories(search_term) + render_category_dropdown(available_categories, search_input, category_display) update_results(current_category, search_term) }) - // on enter, select the first category that matches search_input.addEventListener("keydown", e => { + if (e.key === "ArrowDown") { + if (move_active_category(1)) { + e.preventDefault() + } + return + } + + if (e.key === "ArrowUp") { + if (move_active_category(-1)) { + e.preventDefault() + } + return + } + + if (e.key === "Tab") { + const active_category = get_active_category() + + if (active_category) { + select_category(active_category, search_input, category_display) + e.preventDefault() + } + return + } + if (e.key === "Enter") { const search_term = (e.target as HTMLInputElement).value.toLowerCase() - const available_categories = news_tabs_categories.filter(cat => - cat.toLowerCase().includes(search_term) - ) - - if (available_categories.length > 0) { - category_display.innerText = available_categories[0] - search_input.value = "" - news_search_autocomplete.innerHTML = "" - news_search_autocomplete.style.display = "none" - update_results(available_categories[0], null) - current_category = available_categories[0] - } + const available_categories = get_matching_categories(search_term) + const category_to_select = get_active_category() ?? available_categories[0] + if (category_to_select) { + select_category(category_to_select, search_input, category_display) + } e.preventDefault() } }) @@ -143,7 +282,7 @@ function run_when_ready(): boolean { } if (news_tabs_categories.length === 0) { - return false // don't load if there are no categories yet + return false // don't load if there are no categories yet } if (document.querySelector("#ultrabox-news-search")) { diff --git a/src/content/modules/post_history_tracking/puller.ts b/src/content/modules/post_history_tracking/puller.ts deleted file mode 100644 index 429ef96..0000000 --- a/src/content/modules/post_history_tracking/puller.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { poll_feed } from "../../../background/pull_feed" - -// news itens aren't saved (for some reason) when pull feed is called -// from background.ts -async function main() { - await poll_feed() -} - -main().then(() => { -}) \ No newline at end of file diff --git a/src/content/modules/reduce_width/reduce_width.css b/src/content/modules/reduce_width/reduce_width.css new file mode 100644 index 0000000..a921dcb --- /dev/null +++ b/src/content/modules/reduce_width/reduce_width.css @@ -0,0 +1,4 @@ +#content { + max-width: 1500px; + margin: 0 auto; +} \ No newline at end of file diff --git a/src/content/schooltape/news_search_styles.css b/src/content/schooltape/news_search_styles.css index 817b38f..1d21a5b 100644 --- a/src/content/schooltape/news_search_styles.css +++ b/src/content/schooltape/news_search_styles.css @@ -14,7 +14,13 @@ border-radius: 10px 10px !important; } -.ultrabox-news-search-autocomplete-item:hover { +.ultrabox-news-search-autocomplete-item:hover, +.ultrabox-news-search-autocomplete-item.ultrabox-news-search-autocomplete-item-active { background-color: hsl(var(--ctp-accent)) !important; color: hsl(var(--ctp-crust)) !important; } + +.ultrabox-news-search-autocomplete-hint { + color: hsl(var(--ctp-crust)) !important; + opacity: 0.75; +} diff --git a/src/manifest.json b/src/manifest.json index 45aa761..22f41ed 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "UltraBox", - "version": "1.1.1", + "version": "1.3.0", "description": "A Chrome extension for SchoolBox.", "permissions": ["storage", "activeTab", "scripting", "tabs", "unlimitedStorage"], "host_permissions": [""], diff --git a/src/popup/popup.html b/src/popup/popup.html index 597f731..601b0bf 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -65,15 +65,23 @@

Settings

-
+
- Enable style compatibility with Schooltape. This disables some styles and uses Schooltape styles instead. + Enable style compatibility with Schooltape. This disables some styles and + uses Schooltape styles instead.
@@ -123,6 +131,25 @@

Modules

Box of Books page
+ +
+ + +
+ Shrink the width of the timetable when the launcher is enabled. +
+
Modules
--> +
+
+ + +
+
+ Reduces the width of content on the site and centers it, improving + readability on larger screens. +
+
diff --git a/src/types/settings.d.ts b/src/types/settings.d.ts index 71997e5..a4e09a2 100644 --- a/src/types/settings.d.ts +++ b/src/types/settings.d.ts @@ -10,6 +10,7 @@ export interface Settings { // modules launcher_module?: boolean launcher_module_shortcut?: boolean + reduce_timetable_width?: boolean news_search_module?: boolean recents_list_module?: boolean @@ -17,6 +18,8 @@ export interface Settings { record_post_history?: boolean record_setting_active?: boolean + reduce_content_width?: boolean + // typescript likes to do its own thing sometimes [key: string]: string | boolean | undefined } diff --git a/webpack.config.cjs b/webpack.config.cjs index 35c3189..1857d07 100644 --- a/webpack.config.cjs +++ b/webpack.config.cjs @@ -22,7 +22,6 @@ module.exports = (env, argv) => { inject_css_tools: "./src/content/inject_css_tools.ts", news_search_main: "./src/content/modules/news_search/news_search.ts", display_history: "./src/content/modules/post_history_tracking/content.ts", - history_puller: "./src/content/modules/post_history_tracking/puller.ts", detect_domain: "./src/content/auto_detection/detect_domain.ts", detect_rss_feed: "./src/content/auto_detection/detect_rss_feed.ts", detect_schooltape: "./src/content/auto_detection/detect_schooltape.ts", @@ -119,6 +118,13 @@ module.exports = (env, argv) => { noErrorOnMissing: false, }, + // modules -> reduce width + { + from: "src/content/modules/reduce_width/reduce_width.css", + to: "reduce_width.css", + noErrorOnMissing: false, + }, + // schooltape compatibility CSS { from: "src/content/schooltape/launcher_styles.css",