Compare commits

..

2 Commits

Author SHA1 Message Date
doprz
d291606ead Merge branch 'main' into fix/vite-hmr 2025-10-08 18:08:02 -07:00
Razboy20
ea34a7807f fix: vite hmr 2025-10-07 18:03:21 -05:00
22 changed files with 4336 additions and 2608 deletions

96
.eslintignore Normal file
View File

@@ -0,0 +1,96 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories and management
node_modules/
jspm_packages/
package.json
package-lock.json
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# next.js build output
.next
# Webpack-built output
/dist
# Extension archives
/build
# VsCode
.vscode/*
!.vscode/launch.json
!.vscode/tasks.json
# macOS
.DS_Store
# Terraform
.terraform
# development dependencies
.dev/vue-devtools
.dev/browser-profiles
# IntelliJ
.idea
# Sylelint IntelliJ Plugin Requirement
.stylelintrc.json
# Local environment settings
.env.local
*.svg
config
.eslintrc.js
!.storybook

232
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,232 @@
module.exports = {
root: true,
env: {
browser: true,
es6: true,
node: true,
webextensions: true,
},
ignorePatterns: ['*.html', 'tsconfig.json'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:storybook/recommended',
'airbnb-base',
'airbnb/rules/react',
'airbnb-typescript',
'@unocss',
'prettier',
],
plugins: [
'import',
'import-essentials',
'jsdoc',
'eslint-plugin-tsdoc',
'react-prefer-function-component',
'@typescript-eslint',
'simple-import-sort',
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
debugger: true,
browser: true,
context: true,
JSX: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
ecmaVersion: 2022,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
modules: true,
experimentalObjectRestSpread: true,
},
},
settings: {
react: {
version: 'detect',
},
jsdoc: {
mode: 'typescript',
},
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: './tsconfig.json',
},
},
},
rules: {
'prefer-const': [
'off',
{
destructuring: 'any',
ignoreReadBeforeAssign: false,
},
],
'no-plusplus': 'off',
'no-inner-declarations': 'off',
'sort-imports': 'off',
'no-case-declarations': 'off',
'no-unreachable': 'warn',
'no-constant-condition': 'error',
'space-before-function-paren': 'off',
'no-undef': 'off',
'no-return-await': 'off',
'@typescript-eslint/return-await': 'off',
'@typescript-eslint/no-shadow': ['off'],
'@typescript-eslint/no-use-before-define': ['off'],
'class-methods-use-this': 'off',
'react-hooks/exhaustive-deps': 'warn',
'@typescript-eslint/lines-between-class-members': 'off',
'no-param-reassign': [
'error',
{
props: false,
},
],
'no-console': 'off',
'consistent-return': 'off',
'react/destructuring-assignment': 'off',
'import/prefer-default-export': 'off',
'no-promise-executor-return': 'off',
'import/no-cycle': 'off',
'import/no-extraneous-dependencies': 'off',
'react/jsx-props-no-spreading': 'off',
'react/jsx-no-useless-fragment': [
'error',
{
allowExpressions: true,
},
],
'keyword-spacing': [
'error',
{
before: true,
after: true,
},
],
'no-continue': 'off',
'space-before-blocks': [
'error',
{
functions: 'always',
keywords: 'always',
classes: 'always',
},
],
'react/jsx-filename-extension': [
1,
{
extensions: ['.tsx'],
},
],
'react/no-deprecated': 'warn',
'react/prop-types': 'off',
'react-prefer-function-component/react-prefer-function-component': [
'warn',
{
allowComponentDidCatch: false,
},
],
'react/function-component-definition': 'off',
'react/button-has-type': 'off',
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns-type': 'off',
'jsdoc/newline-after-description': 'off',
'react/require-default-props': 'off',
'jsdoc/require-jsdoc': [
'error',
{
enableFixer: false,
publicOnly: true,
checkConstructors: false,
require: {
ArrowFunctionExpression: true,
ClassDeclaration: true,
ClassExpression: true,
FunctionExpression: true,
},
contexts: [
'MethodDefinition:not([key.name="componentDidMount"]):not([key.name="render"])',
'ArrowFunctionExpression',
'ClassDeclaration',
'ClassExpression',
'ClassProperty:not([key.name="state"]):not([key.name="componentDidMount"])',
'FunctionDeclaration',
'FunctionExpression',
'TSDeclareFunction',
'TSEnumDeclaration',
'TSInterfaceDeclaration',
'TSMethodSignature',
'TSModuleDeclaration',
'TSTypeAliasDeclaration',
],
},
],
'tsdoc/syntax': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/space-before-function-paren': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-empty-interface': 'warn',
'import/no-restricted-paths': [
'error',
{
zones: [
{
target: './src/background',
from: './src/views',
message:
'You cannot import into the `background` directory from the `views` directory (i.e. content script files) because it will break the build!',
},
{
target: './src/views',
from: './src/background',
message:
'You cannot import into the `views` directory from the `background` directory (i.e. background script files) because it will break the build!',
},
{
target: './src/shared',
from: './',
except: ['./src/shared', './node_modules'],
message: 'You cannot import into `shared` from an external directory.',
},
],
},
],
'import/extensions': 'off',
'no-restricted-syntax': [
'error',
'ForInStatement',
'LabeledStatement',
'WithStatement',
{
selector: 'TSEnumDeclaration',
message: "Don't declare enums",
},
],
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'import-essentials/restrict-import-depth': 'error',
'import-essentials/check-path-alias': 'error',
},
};

View File

@@ -1,27 +1,9 @@
## [2.2.2](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.1...v2.2.2) (2025-10-13)
### Features
* add nix flake ([#593](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/593)) ([7b401ad](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7b401add1565ff401bad99745ff9e53b9a7f899f))
* automatically select new or duplicated schedules ([#583](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/583)) ([#589](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/589)) ([2a50f55](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2a50f5580d3dbeb0d66546c23cf29bbb37d80da2))
* **env:** add SENTRY env vars ([8f7e1bc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8f7e1bc0af6336549068e02b80df21d4e8f4ef9c))
* export schedule button add to calendar ([#594](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/594)) ([5994ded](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5994ded8be876cb55174d27d3fdb0832b21a0ff9))
* search result shading ([#617](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/617)) ([be861b8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be861b823cb2cb7f6f4a1f266351eec3fc1c2f99))
* show warning for courses of different semesters ([#570](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/570)) ([2e7dac1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2e7dac1e3eba757231ac07ac966231c08c703a16))
* support summer grades, fix summer course parser ([#596](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/596)) ([2d92dd4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d92dd47f00a44b7d48e92a8ffba94480e4e73f9))
### Bug Fixes
* fix or ignore various eslint warning ([#609](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/609)) ([95de8df](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/95de8df37243b6d59625df515a60442f11b7a9d3))
* limit height of schedule list dropdown in the extension popup ([#543](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/543)) ([eb8141e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/eb8141ee8c3d32bce901457178d50781b78f86dd))
* whitespace wrapping in semester warning ([#629](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/629)) ([46fe591](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/46fe591fa72ef017eea7cfb8aa37d12d8f223926))
## [2.2.1](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.0...v2.2.1) (2025-06-04)
### Features
* add dining app promo ([#598](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/598)) ([be1dccf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be1dccfcb9d052c6b291b50cc53418d6bb645beb))
* inside jokes005 ([#590](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/590)) ([37471ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/37471efb740c7a5828cf3b54bac70954694359d7))
* **release:** v2.2.1 ([234f3d6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/234f3d627d603adf8555b4d0e93106d198918169))
### Bug Fixes

View File

@@ -1,32 +0,0 @@
# UT Registration Plus Code of Conduct
Like the technical community as a whole, the UT Registration Plus team and community is made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the mission - including mentorship, teaching, and connecting people.
Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies equally to founders, mentors and those seeking help and guidance.
This isnt an exhaustive list of things that you cant do. Rather, take it in the spirit in which its intended - a guide to make it easier to enrich all of us and the technical communities in which we participate.
This code of conduct applies to all spaces managed by the UT Registration Plus project or Longhorn Developers. This includes IRC, the mailing lists, the issue tracker, DSF events, and any other forums created by the project team which the community uses for communication. In addition, violations of this code outside these spaces may affect a person's ability to participate within them.
If you believe someone is violating the code of conduct, we ask that you report it by emailing [contact@longhorns.dev](mailto:contact@longhorns.dev). For more details please see our
- **Be friendly and patient.**
- **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
- **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
- **Be respectful.** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. Its important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the UT Registration Plus community should be respectful when dealing with other members as well as with people outside the UT Registration Plus community.
- **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
- Violent threats or language directed against another person.
- Discriminatory jokes and language.
- Posting sexually explicit or violent material.
- Posting (or threatening to post) other people's personally identifying information ("doxing").
- Personal insults, especially those using racist or sexist terms.
- Unwelcome sexual attention.
- Advocating for, or encouraging, any of the above behavior.
- Repeated harassment of others. In general, if someone asks you to stop, then stop.
- **When we disagree, try to understand why.** Disagreements, both social and technical, happen all the time and UT Registration Plus is no exception. It is important that we resolve disagreements and differing views constructively. Remember that were different. The strength of UT Registration Plus comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesnt mean that theyre wrong. Dont forget that it is human to err and blaming each other doesnt get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
Original text courtesy of the [Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html).
## Questions?
If you have questions, please see . If that doesn't answer your questions, feel free to [contact us](mailto:contact@longhorns.dev).

View File

@@ -1,257 +0,0 @@
import { fixupConfigRules } from '@eslint/compat';
import { FlatCompat } from '@eslint/eslintrc';
import js from '@eslint/js';
import importEssentials from 'eslint-plugin-import-essentials';
import jsdoc from 'eslint-plugin-jsdoc';
import reactPreferFunction from 'eslint-plugin-react-prefer-function-component';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import tsdoc from 'eslint-plugin-tsdoc';
import unocss from '@unocss/eslint-config/flat';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
});
export default [
{
ignores: ['*.html', 'tsconfig.json', 'dist/**', 'build/**', 'node_modules/**'],
},
js.configs.recommended,
...fixupConfigRules(
compat.extends(
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:storybook/recommended',
'airbnb-base',
'airbnb/rules/react',
'airbnb-typescript',
'prettier'
)
),
unocss,
{
plugins: {
'import-essentials': importEssentials,
jsdoc,
tsdoc,
'react-prefer-function-component': reactPreferFunction,
'simple-import-sort': simpleImportSort,
},
languageOptions: {
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
debugger: true,
browser: true,
context: true,
JSX: true,
},
ecmaVersion: 2022,
sourceType: 'module',
parserOptions: {
project: './tsconfig.json',
ecmaFeatures: {
jsx: true,
modules: true,
experimentalObjectRestSpread: true,
},
},
},
settings: {
react: {
version: 'detect',
},
jsdoc: {
mode: 'typescript',
},
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: './tsconfig.json',
},
},
},
rules: {
// Disable rules removed in @typescript-eslint v8
'@typescript-eslint/no-throw-literal': 'off',
'prefer-const': [
'off',
{
destructuring: 'any',
ignoreReadBeforeAssign: false,
},
],
'no-plusplus': 'off',
'no-inner-declarations': 'off',
'sort-imports': 'off',
'no-case-declarations': 'off',
'no-unreachable': 'warn',
'no-constant-condition': 'error',
'space-before-function-paren': 'off',
'no-undef': 'off',
'no-return-await': 'off',
'@typescript-eslint/return-await': 'off',
'@typescript-eslint/no-shadow': ['off'],
'@typescript-eslint/no-use-before-define': ['off'],
'class-methods-use-this': 'off',
'react-hooks/exhaustive-deps': 'warn',
'@typescript-eslint/lines-between-class-members': 'off',
'no-param-reassign': [
'error',
{
props: false,
},
],
'no-console': 'off',
'consistent-return': 'off',
'react/destructuring-assignment': 'off',
'import/prefer-default-export': 'off',
'no-promise-executor-return': 'off',
'import/no-cycle': 'off',
'import/no-extraneous-dependencies': 'off',
'react/jsx-props-no-spreading': 'off',
'react/jsx-no-useless-fragment': [
'error',
{
allowExpressions: true,
},
],
'keyword-spacing': [
'error',
{
before: true,
after: true,
},
],
'no-continue': 'off',
'space-before-blocks': [
'error',
{
functions: 'always',
keywords: 'always',
classes: 'always',
},
],
'react/jsx-filename-extension': [
1,
{
extensions: ['.tsx'],
},
],
'react/no-deprecated': 'warn',
'react/prop-types': 'off',
'react-prefer-function-component/react-prefer-function-component': [
'warn',
{
allowComponentDidCatch: false,
},
],
'react/function-component-definition': 'off',
'react/button-has-type': 'off',
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns-type': 'off',
'jsdoc/newline-after-description': 'off',
'react/require-default-props': 'off',
'jsdoc/require-jsdoc': [
'error',
{
enableFixer: false,
publicOnly: true,
checkConstructors: false,
require: {
ArrowFunctionExpression: true,
ClassDeclaration: true,
ClassExpression: true,
FunctionExpression: true,
},
contexts: [
'MethodDefinition:not([key.name="componentDidMount"]):not([key.name="render"])',
'ArrowFunctionExpression',
'ClassDeclaration',
'ClassExpression',
'ClassProperty:not([key.name="state"]):not([key.name="componentDidMount"])',
'FunctionDeclaration',
'FunctionExpression',
'TSDeclareFunction',
'TSEnumDeclaration',
'TSInterfaceDeclaration',
'TSMethodSignature',
'TSModuleDeclaration',
'TSTypeAliasDeclaration',
],
},
],
'tsdoc/syntax': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/space-before-function-paren': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-empty-interface': 'warn',
'import/no-restricted-paths': [
'error',
{
zones: [
{
target: './src/background',
from: './src/views',
message:
'You cannot import into the `background` directory from the `views` directory (i.e. content script files) because it will break the build!',
},
{
target: './src/views',
from: './src/background',
message:
'You cannot import into the `views` directory from the `background` directory (i.e. background script files) because it will break the build!',
},
{
target: './src/shared',
from: './',
except: ['./src/shared', './node_modules'],
message: 'You cannot import into `shared` from an external directory.',
},
],
},
],
'import/extensions': 'off',
'no-restricted-syntax': [
'error',
'ForInStatement',
'LabeledStatement',
'WithStatement',
{
selector: 'TSEnumDeclaration',
message: "Don't declare enums",
},
],
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'import-essentials/restrict-import-depth': 'error',
'import-essentials/check-path-alias': 'error',
},
},
];

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1759831965,
"narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=",
"lastModified": 1754725699,
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c9b6fb798541223bbb396d287d16f43520250518",
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054",
"type": "github"
},
"original": {

View File

@@ -14,29 +14,22 @@
system:
let
pkgs = (import nixpkgs { inherit system; });
commonPackages = with pkgs; [
nodejs_20 # v20.19.5
pnpm_10 # v10.18.0
];
additionalPackages = with pkgs; [
bun
nodePackages.conventional-changelog-cli
sentry-cli
];
in
{
formatter = pkgs.nixfmt-rfc-style;
devShells.default = pkgs.mkShell {
name = "utrp-dev";
buildInputs = commonPackages;
};
buildInputs = with pkgs; [
nodejs_20 # v20.19.4
pnpm_10 # v10.14.0
];
devShells.full = pkgs.mkShell {
name = "utrp-dev-full";
buildInputs = commonPackages ++ additionalPackages;
shellHook = ''
echo "UTRP Nix Flake Environment Loaded"
echo "Node: $(node --version)"
echo "pnpm: $(pnpm --version)"
'';
};
}
);

View File

@@ -1,7 +1,7 @@
{
"name": "ut-registration-plus",
"displayName": "UT Registration Plus",
"version": "2.2.2",
"version": "2.2.1",
"description": "UT Registration Plus is a Chrome extension that allows students to easily register for classes.",
"private": true,
"homepage": "https://github.com/Longhorn-Developers/UT-Registration-Plus",
@@ -15,8 +15,8 @@
"zip:to-publish": "SENTRY_ENV='production' pnpm zip",
"prettier": "prettier src --check",
"prettier:fix": "prettier src --write",
"lint": "eslint src --report-unused-disable-directives",
"lint:fix": "eslint src --report-unused-disable-directives --fix",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
"lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --fix",
"check-types": "tsc --noEmit",
"test": "vitest",
"test:ui": "vitest --ui",
@@ -39,8 +39,8 @@
"@phosphor-icons/react": "^2.1.7",
"@sentry/react": "^8.55.0",
"@tanstack/react-query": "^5.69.0",
"@unocss/vite": "^0.63.6",
"@vitejs/plugin-react": "^4.3.4",
"@unocss/vite": "^66.5.1",
"@vitejs/plugin-react": "^5.0.2",
"chrome-extension-toolkit": "^0.0.54",
"clsx": "^2.1.1",
"conventional-changelog": "^6.0.0",
@@ -66,10 +66,7 @@
"@commitlint/cli": "^19.7.1",
"@commitlint/config-conventional": "^19.7.1",
"@commitlint/types": "^19.5.0",
"@crxjs/vite-plugin": "2.0.0-beta.21",
"@eslint/compat": "^2.0.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.39.1",
"@crxjs/vite-plugin": "2.2.0",
"@iconify-json/bi": "^1.2.2",
"@iconify-json/ic": "^1.2.2",
"@iconify-json/iconoir": "^1.2.7",
@@ -91,7 +88,7 @@
"@types/conventional-changelog": "^3.1.5",
"@types/gulp": "^4.0.17",
"@types/gulp-zip": "^4.0.4",
"@types/node": "^22.13.5",
"@types/node": "^22.18.0",
"@types/prompts": "^2.4.9",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
@@ -99,16 +96,16 @@
"@types/semantic-release": "^20.0.6",
"@types/semver": "^7.5.8",
"@types/sql.js": "^1.4.9",
"@typescript-eslint/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.47.0",
"@unocss/eslint-config": "^0.63.6",
"@unocss/postcss": "^0.63.6",
"@unocss/preset-uno": "^0.63.6",
"@unocss/preset-web-fonts": "^0.63.6",
"@unocss/reset": "^0.63.6",
"@unocss/transformer-directives": "^0.63.6",
"@unocss/transformer-variant-group": "^0.63.6",
"@vitejs/plugin-react-swc": "^3.8.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@unocss/eslint-config": "^66.5.1",
"@unocss/postcss": "^66.5.1",
"@unocss/preset-web-fonts": "^66.5.1",
"@unocss/preset-wind4": "^66.5.1",
"@unocss/reset": "^66.5.1",
"@unocss/transformer-directives": "^66.5.1",
"@unocss/transformer-variant-group": "^66.5.1",
"@vitejs/plugin-react-swc": "^4.0.1",
"@vitest/coverage-v8": "^2.1.9",
"@vitest/ui": "^2.1.9",
"chalk": "^5.4.1",
@@ -117,38 +114,38 @@
"cssnano-preset-advanced": "^7.0.6",
"dotenv": "^16.4.7",
"es-module-lexer": "^1.6.0",
"eslint": "^9.39.1",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^18.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.8.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-import-essentials": "^0.2.1",
"eslint-plugin-jsdoc": "^61.2.1",
"eslint-plugin-jsdoc": "^50.6.3",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-prefer-function-component": "^3.4.0",
"eslint-plugin-react-refresh": "^0.4.19",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-storybook": "^10.0.8",
"eslint-plugin-storybook": "^0.9.0",
"eslint-plugin-tsdoc": "^0.3.0",
"gulp": "^5.0.0",
"gulp-execa": "^7.0.1",
"gulp-zip": "^6.1.0",
"path": "^0.12.7",
"postcss": "^8.5.3",
"prettier": "^3.6.2",
"prettier": "^3.5.2",
"react-dev-utils": "^12.0.1",
"semantic-release": "^24.2.3",
"storybook": "^8.6.0",
"typescript": "^5.7.3",
"unocss": "^0.63.6",
"unocss-preset-primitives": "0.0.2-beta.1",
"unocss": "^66.5.1",
"unocss-preset-primitives": "0.0.2-beta.2",
"unplugin-icons": "^0.19.3",
"vite": "^5.4.20",
"vite-plugin-inspect": "^0.8.9",
"vite": "^7.1.5",
"vite-plugin-inspect": "^11.3.3",
"vitest": "^2.1.9"
},
"engineStrict": true,
@@ -157,12 +154,12 @@
},
"pnpm": {
"patchedDependencies": {
"@crxjs/vite-plugin@2.0.0-beta.21": "patches/@crxjs__vite-plugin@2.0.0-beta.21.patch",
"@unocss/vite": "patches/@unocss__vite.patch"
"@unocss/vite": "patches/@unocss__vite.patch",
"@crxjs/vite-plugin": "patches/@crxjs__vite-plugin.patch"
},
"overrides": {
"es-module-lexer": "^1.5.4"
}
"onlyBuiltDependencies": [
"@swc/core"
]
},
"volta": {
"node": "20.19.4",

File diff suppressed because it is too large Load Diff

View File

@@ -1,105 +0,0 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index 5c3f6291168987c56b816428080e6f1fe9de7107..abaf6290fe9454ae036a81eacbe7dc3be2fdfbc3 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -499,16 +499,43 @@ ${sourceMap}
}),
mergeMap(async ({ target, code, deps }) => {
await lexer.init;
- const [imports] = lexer.parse(code, fileName);
+ const [imports, exports] = lexer.parse(code, fileName);
const depSet = new Set(deps);
const magic = new MagicString(code);
- for (const i of imports)
+ for (const i of imports) {
if (i.n) {
depSet.add(i.n);
const fileName2 = getFileName({ type: "module", id: i.n });
const fullImport = code.substring(i.s, i.e);
- magic.overwrite(i.s, i.e, fullImport.replace(i.n, `/${fileName2}`));
+ const hmrTimestamp = fullImport.match(/\bt=\d{13}&?\b/);
+ magic.overwrite(
+ i.s,
+ i.e,
+ fullImport.replace(
+ i.n,
+ `/${fileName2}${hmrTimestamp ? `?${hmrTimestamp[0]}` : ""}`
+ )
+ );
+ }
+ }
+ for (const e of exports) {
+ if (e.n === "default") {
+ const regex = /\s+['"](.*)['"]/y;
+ regex.lastIndex = e.e;
+ const fullExport = regex.exec(code)?.[1];
+ if (!fullExport)
+ continue;
+ const start = regex.lastIndex - fullExport.length - 1;
+ const end = regex.lastIndex - 1;
+ if (fullExport.startsWith("/node_modules")) {
+ magic.overwrite(
+ start,
+ end,
+ `http://localhost:5173${fullExport}`
+ );
+ }
}
+ }
return { target, source: magic.toString(), deps: [...depSet] };
})
);
@@ -1229,10 +1256,14 @@ const pluginHMR = () => {
handleHotUpdate({ modules, server }) {
const { root } = server.config;
const relFiles = /* @__PURE__ */ new Set();
- for (const m of modules)
+ function getRelFile(file) {
+ return file.startsWith(root) ? file.slice(server.config.root.length) : file;
+ }
+ for (const m of modules) {
if (m.id?.startsWith(root)) {
relFiles.add(m.id.slice(server.config.root.length));
}
+ }
if (inputManifestFiles.background.length) {
const background = prefix$1("/", inputManifestFiles.background[0]);
if (relFiles.has(background) || modules.some(isImporter(join(server.config.root, background)))) {
@@ -1244,7 +1275,14 @@ const pluginHMR = () => {
for (const [key, script] of contentScripts)
if (key === script.id) {
if (relFiles.has(script.id) || modules.some(isImporter(join(server.config.root, script.id)))) {
- relFiles.forEach((relFile) => update(relFile));
+ modules.filter((mod) => mod.id?.startsWith(root)).forEach((mod) => {
+ update(getRelFile(mod.id));
+ if (mod.file?.endsWith(".scss")) {
+ mod.importers.forEach((imp) => {
+ update(getRelFile(imp.id));
+ });
+ }
+ });
}
}
}
@@ -1882,7 +1920,7 @@ const pluginWebAccessibleResources = () => {
if (contentScripts.size > 0) {
const viteManifest = parseJsonAsset(
bundle,
- "manifest.json"
+ ".vite/manifest.json"
);
const viteFiles = /* @__PURE__ */ new Map();
for (const [, file] of Object.entries(viteManifest))
diff --git a/package.json b/package.json
index e0c47ae66ff399ad3a78abf38d8d93d1f038c55d..f84eb09ffbb5c41094935dd06e04ffe831e2d05a 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,7 @@
"connect-injector": "^0.4.4",
"convert-source-map": "^1.7.0",
"debug": "^4.3.3",
- "es-module-lexer": "^0.10.0",
+ "es-module-lexer": "^1.4.1",
"fast-glob": "^3.2.11",
"fs-extra": "^10.0.1",
"jsesc": "^3.0.2",

View File

@@ -1,8 +1,8 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index 7210f5fd650a0b7bb36b467fff85feb0d8e4ec63..c8f98bd314daec0b91c514ea9d9fc2b79cea8502 100644
index 3b94c870f00ce7e70be208199db48a3bd5569830..4aaaec7d5b656f1c4066c6295d5228d746420d17 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -369,15 +369,15 @@ const VIRTUAL_ENTRY_ALIAS = [
@@ -24,8 +24,8 @@ const VIRTUAL_ENTRY_ALIAS = [
/^(?:virtual:)?uno(?::(.+))?\.css(\?.*)?$/
];
const LAYER_MARK_ALL = "__ALL__";
@@ -10,27 +10,19 @@ index 7210f5fd650a0b7bb36b467fff85feb0d8e4ec63..c8f98bd314daec0b91c514ea9d9fc2b7
-const RESOLVED_ID_RE = /[/\\]__uno(?:_(.*?))?\.css$/;
+const RESOLVED_ID_WITH_QUERY_RE = /[/\\]uno(_.*?)?\.css(\?.*)?$/;
+const RESOLVED_ID_RE = /[/\\]uno(?:_(.*?))?\.css$/;
function resolveId(id) {
if (id.match(RESOLVED_ID_WITH_QUERY_RE))
return id;
const defaultPipelineExclude = [cssIdRE];
const defaultPipelineInclude = [/\.(vue|svelte|[jt]sx|vine.ts|mdx?|astro|elm|php|phtml|html)($|\?)/];
@@ -468,7 +468,7 @@ function resolveId(id, importer) {
for (const alias of VIRTUAL_ENTRY_ALIAS) {
const match = id.match(alias);
if (match) {
- return match[1] ? `/__uno_${match[1]}.css` : "/__uno.css";
+ return match[1] ? `/uno_${match[1]}.css` : "/uno.css";
}
}
}
@@ -652,7 +652,7 @@ function GlobalModeBuildPlugin(ctx) {
css = await applyCssTransform(css, fakeCssId, options.dir, this);
const transformHandler = "handler" in cssPost.transform ? cssPost.transform.handler : cssPost.transform;
if (isLegacy) {
- await transformHandler.call({}, css, "/__uno.css");
+ await transformHandler.call({}, css, "/uno.css");
} else {
const hash = getHash(css);
await transformHandler.call({}, getHashPlaceholder(hash), fakeCssId);
@@ -914,7 +914,7 @@ function GlobalModeDevPlugin({ uno, tokens, tasks, flushTasks, affectedModules,
- let virtual = match[1] ? `__uno_${match[1]}.css` : "__uno.css";
+ let virtual = match[1] ? `uno_${match[1]}.css` : "uno.css";
virtual += match[2] || "";
if (importer)
virtual = resolve$1(importer, "..", virtual);
@@ -813,7 +813,7 @@ function GlobalModeDevPlugin(ctx) {
const { hash, css } = await generateCSS(layer);
return {
// add hash to the chunk of CSS that it will send back to client to check if there is new CSS generated
@@ -39,7 +31,7 @@ index 7210f5fd650a0b7bb36b467fff85feb0d8e4ec63..c8f98bd314daec0b91c514ea9d9fc2b7
map: { mappings: "" }
};
},
@@ -933,7 +933,7 @@ function GlobalModeDevPlugin({ uno, tokens, tasks, flushTasks, affectedModules,
@@ -832,7 +832,7 @@ function GlobalModeDevPlugin(ctx) {
if (layer && code.includes("import.meta.hot")) {
let hmr = `
try {

4190
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,12 @@ const manifest = defineManifest(async () => ({
host_permissions: process.env.MODE === 'development' ? [...HOST_PERMISSIONS, '<all_urls>'] : HOST_PERMISSIONS,
action: {
default_popup: 'src/pages/popup/index.html',
default_icon: `icons/icon_${mode}_32.png`,
default_icon: {
'16': `icons/icon_${mode}_16.png`,
'32': `icons/icon_${mode}_32.png`,
'48': `icons/icon_${mode}_48.png`,
'128': `icons/icon_${mode}_128.png`,
},
},
icons: {
'16': `icons/icon_${mode}_16.png`,

View File

@@ -10,9 +10,8 @@ const support = getSiteSupport(window.location.href);
const renderComponent = (Component: React.ComponentType) => {
const container = document.createElement('div');
container.id = 'extension-root';
document.body.appendChild(container);
// all components are portaled away, not actually rendered to screen
createRoot(container).render(
<React.StrictMode>
<Component />

View File

@@ -24,8 +24,6 @@ export interface IOptionsStore {
/** whether the promo should be shown */
showUTDiningPromo: boolean;
/** whether users are allowed to bypass the 10 schedule limit */
allowMoreSchedules: boolean;
}
export const OptionsStore = createSyncStore<IOptionsStore>({
@@ -36,7 +34,6 @@ export const OptionsStore = createSyncStore<IOptionsStore>({
alwaysOpenCalendarInNewTab: false,
showCalendarSidebar: true,
showUTDiningPromo: true,
allowMoreSchedules: false,
});
/**
@@ -53,7 +50,6 @@ export const initSettings = async () =>
alwaysOpenCalendarInNewTab: await OptionsStore.get('alwaysOpenCalendarInNewTab'),
showCalendarSidebar: await OptionsStore.get('showCalendarSidebar'),
showUTDiningPromo: await OptionsStore.get('showUTDiningPromo'),
allowMoreSchedules: await OptionsStore.get('allowMoreSchedules'),
}) satisfies IOptionsStore;
// Clothing retailer right

View File

@@ -14,21 +14,21 @@ interface LinkItem {
}
const links: LinkItem[] = [
// {
// text: "Fall '25 Course Schedule",
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20259/',
// },
// {
// text: 'Course Schedule Archives',
// url: 'https://registrar.utexas.edu/schedules/archive',
// },
{
text: "Spring '26 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20262/',
},
{
text: 'Other Course Schedules',
url: 'https://registrar.utexas.edu/schedules',
text: "Fall '25 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20259/',
},
{
text: "Summer '25 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20256/',
},
{
text: 'Course Schedule Archives',
url: 'https://registrar.utexas.edu/schedules/archive',
},
{
text: 'My Degree Audit (IDA)',

View File

@@ -4,7 +4,7 @@ import type { ThemeColor } from '@shared/types/ThemeColors';
import { getThemeColorHexByName, getThemeColorRgbByName } from '@shared/util/themeColors';
import Text from '@views/components/common/Text/Text';
import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import React from 'react';
interface Props {
className?: string;
@@ -21,8 +21,9 @@ interface Props {
}
/**
* A reusable input button component that follows Button.tsx consistency.
* Now supports drag-and-drop file uploads (issue #446).
* A reusable input button component that follows the Button.tsx consistency
*
* @returns
*/
export default function FileUpload({
className,
@@ -42,74 +43,22 @@ export default function FileUpload({
const isIconOnly = !children && !!icon;
const colorHex = getThemeColorHexByName(color);
const colorRgb = getThemeColorRgbByName(color)?.join(' ');
const inputRef = useRef<HTMLInputElement>(null);
const [isDragging, setIsDragging] = useState(false);
// Convert accept array to comma-separated list
// Convert accept to a comma-separated string if it's an array
const acceptValue = Array.isArray(accept) ? accept.join(',') : accept;
// --- Prevent Chrome from opening the file on drop anywhere else ---
useEffect(() => {
const preventDefault = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
window.addEventListener('dragover', preventDefault);
window.addEventListener('drop', preventDefault);
return () => {
window.removeEventListener('dragover', preventDefault);
window.removeEventListener('drop', preventDefault);
};
}, []);
// ------------------------------------------------------------------
// --- Local drag and drop handlers for this button only -------------
const handleDrop = (event: React.DragEvent<HTMLLabelElement>) => {
event.preventDefault();
event.stopPropagation();
setIsDragging(false);
if (disabled) return;
const file = event.dataTransfer.files?.[0];
if (file && inputRef.current && onChange) {
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
inputRef.current.files = dataTransfer.files;
// Trigger change event manually
onChange({ target: inputRef.current } as React.ChangeEvent<HTMLInputElement>);
}
};
const handleDragOver = (event: React.DragEvent<HTMLLabelElement>) => {
event.preventDefault();
event.stopPropagation();
if (!disabled) setIsDragging(true);
};
const handleDragLeave = (event: React.DragEvent<HTMLLabelElement>) => {
event.preventDefault();
event.stopPropagation();
setIsDragging(false);
};
// ------------------------------------------------------------------
return (
<label
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
style={{
style={
{
...style,
color: disabled ? 'ut-gray' : colorHex,
backgroundColor: `rgb(${colorRgb} / var(--un-bg-opacity))`,
}}
backgroundColor: `rgb(${colorRgb} / var(--un-bg-opacity)`,
} satisfies React.CSSProperties
}
className={clsx(
'btn transition-colors select-none',
'btn',
{
'ring-2 ring-offset-2 ring-blue-400': isDragging && !disabled,
'text-white! bg-opacity-100 hover:enabled:shadow-md active:enabled:shadow-sm shadow-black/20':
variant === 'filled',
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border stroke-width-[1px]':
@@ -121,7 +70,6 @@ export default function FileUpload({
'h-[35px] w-[35px] p-spacing-2': size === 'small' && isIconOnly,
'h-6 p-spacing-2': size === 'mini' && !isIconOnly,
'h-6 w-6 p-0': size === 'mini' && isIconOnly,
'opacity-60 cursor-not-allowed': disabled,
},
className
)}
@@ -137,7 +85,6 @@ export default function FileUpload({
</Text>
)}
<input
ref={inputRef}
type='file'
{...(accept ? { accept: acceptValue } : {})}
className='hidden'

View File

@@ -175,11 +175,11 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
description: (
<>
The section you&apos;re adding is for{' '}
<span className='whitespace-nowrap text-ut-burntorange'>
<span className='text-ut-burntorange whitespace-nowrap'>
{course.semester.season} {course.semester.year}
</span>
, but your current schedule contains sections in{' '}
<span className='whitespace-nowrap text-ut-burntorange'>{activeSemesters}</span>. Mixing
<span className='text-ut-burntorange whitespace-nowrap'>{activeSemesters}</span>. Mixing
semesters in one schedule may cause confusion.
</>
),

View File

@@ -92,7 +92,6 @@ export default function Settings(): JSX.Element {
const [loadAllCourses, setLoadAllCourses] = useState<boolean>(false);
const [_enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
const [calendarNewTab, setCalendarNewTab] = useState<boolean>(false);
const [increaseScheduleLimit, setIncreaseScheduleLimit] = useState<boolean>(false);
const showMigrationDialog = useMigrationDialog();
@@ -127,7 +126,6 @@ export default function Settings(): JSX.Element {
enableScrollToLoad,
enableDataRefreshing,
alwaysOpenCalendarInNewTab,
allowMoreSchedules,
} = await initSettings();
setEnableCourseStatusChips(enableCourseStatusChips);
// setShowTimeLocation(enableTimeAndLocationInPopup);
@@ -135,7 +133,6 @@ export default function Settings(): JSX.Element {
setLoadAllCourses(enableScrollToLoad);
setEnableDataRefreshing(enableDataRefreshing);
setCalendarNewTab(alwaysOpenCalendarInNewTab);
setIncreaseScheduleLimit(allowMoreSchedules);
};
const initDS = async () => {
@@ -190,15 +187,6 @@ export default function Settings(): JSX.Element {
// console.log('alwaysOpenCalendarInNewTab', newValue);
});
const l6 = OptionsStore.listen('alwaysOpenCalendarInNewTab', async ({ newValue }) => {
setCalendarNewTab(newValue);
// console.log('alwaysOpenCalendarInNewTab', newValue);
});
const l7 = OptionsStore.listen('allowMoreSchedules', async ({ newValue }) => {
setIncreaseScheduleLimit(newValue);
});
// Remove listeners when the component is unmounted
return () => {
OptionsStore.removeListener(l1);
@@ -206,8 +194,6 @@ export default function Settings(): JSX.Element {
OptionsStore.removeListener(l3);
OptionsStore.removeListener(l4);
OptionsStore.removeListener(l5);
OptionsStore.removeListener(l6);
OptionsStore.removeListener(l7);
DevStore.removeListener(ds_l1);
@@ -246,18 +232,21 @@ export default function Settings(): JSX.Element {
const handleImportClick = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
if (file) {
const reader = new FileReader();
reader.onload = async e => {
try {
const text = await file.text();
const data = JSON.parse(text);
await importSchedule(data);
alert('Schedule imported successfully.');
const result = e.target?.result as string;
const jsonObject = JSON.parse(result);
await importSchedule(jsonObject);
} catch (error) {
console.error('Error importing schedule:', error);
alert('Failed to import schedule. Make sure the file is a valid .json format.');
console.error('Invalid import file!');
}
};
reader.readAsText(file);
}
};
// const handleAddCourseByLink = async () => {
// // todo: Use a proper modal instead of a prompt
// const link: string | null = prompt('Enter course link');
@@ -463,25 +452,6 @@ export default function Settings(): JSX.Element {
/>
</div>
<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Allow more than 10 schedules
</Text>
<p className='text-sm text-gray-600'>
Allow bypassing the 10-schedule limit. Intended for advisors or staff who
need to create many schedules on behalf of students.
</p>
</div>
<SwitchButton
isChecked={increaseScheduleLimit}
onChange={() => {
setIncreaseScheduleLimit(!increaseScheduleLimit);
OptionsStore.set('allowMoreSchedules', !increaseScheduleLimit);
}}
/>
</div>
<Divider size='auto' orientation='horizontal' />
<div className='flex items-center justify-between'>

View File

@@ -1,8 +1,5 @@
import { background } from '@shared/messages';
import { OptionsStore } from '@shared/storage/OptionsStore';
import { CRX_PAGES } from '@shared/types/CRXPages';
import useSchedules from '@views/hooks/useSchedules';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback } from 'react';
import { Button } from '../components/common/Button';
import { usePrompt } from '../components/common/DialogProvider/DialogProvider';
@@ -20,33 +17,8 @@ const SCHEDULE_LIMIT = 10;
export function useEnforceScheduleLimit(): () => boolean {
const [, schedules] = useSchedules();
const showDialog = usePrompt();
const [allowMoreSchedules, setAllowMoreSchedules] = useState<boolean>(false);
useEffect(() => {
let mounted = true;
(async () => {
try {
const val = await OptionsStore.get('allowMoreSchedules');
if (mounted) setAllowMoreSchedules(val ?? false);
} catch (err) {
console.error('Failed to read allowMoreSchedules from OptionsStore:', err);
}
})();
const listener = OptionsStore.listen('allowMoreSchedules', async ({ newValue }) => {
setAllowMoreSchedules(newValue);
});
return () => {
mounted = false;
OptionsStore.removeListener(listener);
};
}, []);
return useCallback(() => {
// If user has enabled bypass, allow creating more schedules
if (allowMoreSchedules) return true;
if (schedules.length >= SCHEDULE_LIMIT) {
showDialog({
title: `You have too many schedules!`,
@@ -55,33 +27,19 @@ export function useEnforceScheduleLimit(): () => boolean {
<>
To encourage organization,{' '}
<span className='text-ut-burntorange'>please consider deleting any unused schedules</span> you
may have. You can increase the limit in the settings if its really necessary.
may have.
</>
),
buttons: close => (
<>
<Button
variant='outline'
color='ut-black'
onClick={() => {
close();
// open options/settings page so user can enable bypass if they are advising staff
const url = chrome.runtime.getURL(CRX_PAGES.OPTIONS);
background.openNewTab({ url });
}}
>
Open Settings
</Button>
<Button variant='filled' color='ut-burntorange' onClick={close}>
I understand
</Button>
</>
),
});
return false;
}
return true;
}, [schedules, showDialog, allowMoreSchedules]);
}, [schedules, showDialog]);
}

View File

@@ -1,4 +1,4 @@
import presetUno from '@unocss/preset-uno';
import presetWind4 from '@unocss/preset-wind4';
import presetWebFonts from '@unocss/preset-web-fonts';
import transformerDirectives from '@unocss/transformer-directives';
import transformerVariantGroup from '@unocss/transformer-variant-group';
@@ -50,7 +50,8 @@ export default defineConfig({
},
],
presets: [
presetUno(),
presetWind4(),
// todo: for some reason, breaking eslint ._.
presetWebFonts({
provider: 'none',
fonts: {

View File

@@ -207,6 +207,9 @@ export default defineConfig({
hmr: {
clientPort: 5173,
},
cors: {
origin: [/chrome-extension:\/\//],
},
proxy: {
'/debug.html': {
target: 'http://localhost:5173',
@@ -256,16 +259,16 @@ export default defineConfig({
},
},
},
test: {
coverage: {
provider: 'v8',
},
},
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler',
},
},
},
// test: {
// coverage: {
// provider: "v8",
// },
// },
// css: {
// preprocessorOptions: {
// scss: {
// api: "modern-compiler",
// },
// },
// },
});