;
diff --git a/frontend/app/src/app/modules/data/data-icon.pipe.ts b/frontend/app/src/app/modules/data/data-icon.pipe.ts
index d33deb3c..084ac43a 100644
--- a/frontend/app/src/app/modules/data/data-icon.pipe.ts
+++ b/frontend/app/src/app/modules/data/data-icon.pipe.ts
@@ -31,7 +31,7 @@ export class DataIconPipe implements PipeTransform {
/**
* Provide the icon name from the data type
*/
- transform(type: SCThingType): string {
+ transform(type: SCThingType) {
return this.typeIconMap[type];
}
}
diff --git a/frontend/app/src/app/modules/data/elements/title-card.component.ts b/frontend/app/src/app/modules/data/elements/title-card.component.ts
index 9c18fc9e..df529895 100644
--- a/frontend/app/src/app/modules/data/elements/title-card.component.ts
+++ b/frontend/app/src/app/modules/data/elements/title-card.component.ts
@@ -18,8 +18,8 @@ import {SCThings} from '@openstapps/core';
import {SCIcon} from '../../../util/ion-icon/icon';
const AccordionButtonState = {
- collapsed: SCIcon`expand_more`,
- expanded: SCIcon`expand_less`,
+ collapsed: SCIcon.expand_more,
+ expanded: SCIcon.expand_less,
};
@Component({
@@ -35,7 +35,8 @@ export class TitleCardComponent implements OnInit, OnChanges {
@ViewChild('accordionTextArea') accordionTextArea: ElementRef;
- buttonState = AccordionButtonState.collapsed;
+ buttonState: (typeof AccordionButtonState)[keyof typeof AccordionButtonState] =
+ AccordionButtonState.collapsed;
buttonShown = true;
diff --git a/frontend/app/src/app/modules/menu/navigation/navigation.html b/frontend/app/src/app/modules/menu/navigation/navigation.html
index 707329c7..fb223962 100644
--- a/frontend/app/src/app/modules/menu/navigation/navigation.html
+++ b/frontend/app/src/app/modules/menu/navigation/navigation.html
@@ -35,13 +35,13 @@
lines="none"
class="menu-category"
>
-
+
{{ category.translations[language]?.title | titlecase }}
}
@for (item of category.items; track item) {
-
+
{{ item.translations[language]?.title | titlecase }}
}
diff --git a/frontend/app/src/app/modules/menu/navigation/tabs.template.html b/frontend/app/src/app/modules/menu/navigation/tabs.template.html
index bd2c929a..56e47cc6 100644
--- a/frontend/app/src/app/modules/menu/navigation/tabs.template.html
+++ b/frontend/app/src/app/modules/menu/navigation/tabs.template.html
@@ -41,7 +41,7 @@
@for (category of menu; track category; let isFirst = $first) {
-
+
{{ category.translations[language]?.title | titlecase }}
}
diff --git a/frontend/app/src/app/modules/profile/page/profile-page-section.html b/frontend/app/src/app/modules/profile/page/profile-page-section.html
index 74c2dda6..308b92ad 100644
--- a/frontend/app/src/app/modules/profile/page/profile-page-section.html
+++ b/frontend/app/src/app/modules/profile/page/profile-page-section.html
@@ -35,7 +35,7 @@
>
@if (link.icon) {
-
+
}
{{ 'name' | translateSimple: link }}
diff --git a/frontend/app/src/app/util/ion-icon/icon-match.d.mts b/frontend/app/src/app/util/ion-icon/icon-match.d.mts
new file mode 100644
index 00000000..60a8dc5b
--- /dev/null
+++ b/frontend/app/src/app/util/ion-icon/icon-match.d.mts
@@ -0,0 +1,5 @@
+export function matchTagProperties(tag: string): RegExp;
+
+export function matchPropertyContent(properties: string[]): RegExp;
+
+export function matchPropertyAccess(objectName: string): RegExp;
diff --git a/frontend/app/src/app/util/ion-icon/icon-match.ts b/frontend/app/src/app/util/ion-icon/icon-match.mjs
similarity index 73%
rename from frontend/app/src/app/util/ion-icon/icon-match.ts
rename to frontend/app/src/app/util/ion-icon/icon-match.mjs
index be9d985c..2697d35c 100644
--- a/frontend/app/src/app/util/ion-icon/icon-match.ts
+++ b/frontend/app/src/app/util/ion-icon/icon-match.mjs
@@ -14,17 +14,24 @@
*/
/**
- *
+ * @param {string} tag
*/
-export function matchTagProperties(tag: string) {
+export function matchTagProperties(tag) {
return new RegExp(`(?<=<${tag})[\\s\\S]*?(?=>\\s*<\\/${tag}\\s*>)`, 'g');
}
/**
- *
+ * @param {string[]} properties
*/
-export function matchPropertyContent(properties: string[]) {
+export function matchPropertyContent(properties) {
const names = properties.join('|');
return new RegExp(`((?<=(${names})=")[\\w-]+(?="))|((?<=\\[(${names})]="')[\\w-]+(?='"))`, 'g');
}
+
+/**
+ * @param {string} objectName
+ */
+export function matchPropertyAccess(objectName) {
+ return new RegExp(`(?<=${objectName}\\s*\\.\\s*)\\w+`, 'g');
+}
diff --git a/frontend/app/src/app/util/ion-icon/icon-match.spec.ts b/frontend/app/src/app/util/ion-icon/icon-match.spec.ts
index 088d0676..6b594c5a 100644
--- a/frontend/app/src/app/util/ion-icon/icon-match.spec.ts
+++ b/frontend/app/src/app/util/ion-icon/icon-match.spec.ts
@@ -12,9 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-/* eslint-disable unicorn/no-null */
-
-import {matchPropertyContent, matchTagProperties} from './icon-match';
+import {matchPropertyAccess, matchPropertyContent, matchTagProperties} from './icon-match.mjs';
describe('matchTagProperties', function () {
const regex = matchTagProperties('test');
@@ -59,3 +57,30 @@ describe('matchPropertyContent', function () {
expect(`no="content" [no]="'content'"`.match(regex)).toEqual(null);
});
});
+
+describe('matchPropertyAccess', function () {
+ const property = '0_20a_boninAo0_';
+ const object = 'test';
+ const regex = matchPropertyAccess(object);
+
+ it('should match property access', function () {
+ expect(`${object}.${property}`.match(regex)).toEqual([property]);
+ });
+
+ it('should respect whitespace', function () {
+ expect(`${object}. ${property}`.match(regex)).toEqual([property]);
+ expect(`${object} .${property}`.match(regex)).toEqual([property]);
+ expect(`${object} . ${property}`.match(regex)).toEqual([property]);
+ expect(`${object} \n . \n ${property}`.match(regex)).toEqual([property]);
+ });
+
+ it('should not include invalid trailing stuff', function () {
+ expect(`${object}.${property}!`.match(regex)).toEqual([property]);
+ expect(`${object}.${property}.`.match(regex)).toEqual([property]);
+ expect(`${object}.${property}-`.match(regex)).toEqual([property]);
+ expect(`${object}.${property}]`.match(regex)).toEqual([property]);
+ expect(`${object}.${property}}`.match(regex)).toEqual([property]);
+ expect(`${object}.${property};`.match(regex)).toEqual([property]);
+ expect(`${object}.${property}:`.match(regex)).toEqual([property]);
+ });
+});
diff --git a/frontend/app/src/app/util/ion-icon/icon.ts b/frontend/app/src/app/util/ion-icon/icon.ts
index d083989e..3da2fd38 100644
--- a/frontend/app/src/app/util/ion-icon/icon.ts
+++ b/frontend/app/src/app/util/ion-icon/icon.ts
@@ -13,9 +13,8 @@
* this program. If not, see .
*/
-/**
- * A noop function to aid parsing icon names
- */
-export function SCIcon(strings: TemplateStringsArray, ..._keys: string[]): string {
- return strings.join('');
-}
+import {MaterialSymbol} from 'material-symbols';
+
+export const SCIcon = new Proxy({} as {[key in MaterialSymbol]: key}, {
+ get: (_target, prop: string) => prop as MaterialSymbol,
+});
diff --git a/frontend/app/src/app/util/ion-icon/ion-back-button.directive.ts b/frontend/app/src/app/util/ion-icon/ion-back-button.directive.ts
index 1b742ba9..edfadb79 100644
--- a/frontend/app/src/app/util/ion-icon/ion-back-button.directive.ts
+++ b/frontend/app/src/app/util/ion-icon/ion-back-button.directive.ts
@@ -48,8 +48,8 @@ export class IonBackButtonDirective extends IconReplacer implements OnInit {
replace() {
this.replaceIcon(this.host.querySelector('.button-inner'), {
- md: SCIcon`arrow_back`,
- ios: SCIcon`arrow_back_ios`,
+ md: SCIcon.arrow_back,
+ ios: SCIcon.arrow_back_ios,
size: 24,
});
}
diff --git a/frontend/app/src/app/util/ion-icon/ion-breadcrumb.directive.ts b/frontend/app/src/app/util/ion-icon/ion-breadcrumb.directive.ts
index 2e9de757..3b312a35 100644
--- a/frontend/app/src/app/util/ion-icon/ion-breadcrumb.directive.ts
+++ b/frontend/app/src/app/util/ion-icon/ion-breadcrumb.directive.ts
@@ -28,7 +28,7 @@ export class IonBreadcrumbDirective extends IconReplacer {
this.replaceIcon(
this.host.querySelector('span[part="separator"]'),
{
- name: SCIcon`arrow_forward_ios`,
+ name: SCIcon.arrow_forward_ios,
size: 16,
style: `color: var(--ion-color-tint);`,
},
@@ -37,7 +37,7 @@ export class IonBreadcrumbDirective extends IconReplacer {
this.replaceIcon(
this.host.querySelector('button[part="collapsed-indicator"]'),
{
- name: SCIcon`more_horiz`,
+ name: SCIcon.more_horiz,
size: 24,
},
'-collapsed',
diff --git a/frontend/app/src/app/util/ion-icon/ion-icon.directive.ts b/frontend/app/src/app/util/ion-icon/ion-icon.directive.ts
index 80b96bc8..99ad2d2c 100644
--- a/frontend/app/src/app/util/ion-icon/ion-icon.directive.ts
+++ b/frontend/app/src/app/util/ion-icon/ion-icon.directive.ts
@@ -28,6 +28,7 @@ import {
} from '@angular/core';
import {IconComponent} from './icon.component';
import {IonIcon} from '@ionic/angular';
+import {MaterialSymbol} from 'material-symbols';
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
@@ -40,7 +41,7 @@ const noopProperty = {
selector: 'ion-icon',
})
export class IonIconDirective implements OnInit, OnDestroy, OnChanges {
- @Input() name: string;
+ @Input() name: MaterialSymbol;
@Input() md: string;
diff --git a/frontend/app/src/app/util/ion-icon/ion-reorder.directive.ts b/frontend/app/src/app/util/ion-icon/ion-reorder.directive.ts
index 2a8f74e9..d53a8463 100644
--- a/frontend/app/src/app/util/ion-icon/ion-reorder.directive.ts
+++ b/frontend/app/src/app/util/ion-icon/ion-reorder.directive.ts
@@ -26,7 +26,7 @@ export class IonReorderDirective extends IconReplacer {
replace() {
this.replaceIcon(this.host, {
- name: SCIcon`reorder`,
+ name: SCIcon.reorder,
size: 24,
});
}
diff --git a/frontend/app/src/app/util/ion-icon/ion-searchbar.directive.ts b/frontend/app/src/app/util/ion-icon/ion-searchbar.directive.ts
index d9f6d324..eef39ce8 100644
--- a/frontend/app/src/app/util/ion-icon/ion-searchbar.directive.ts
+++ b/frontend/app/src/app/util/ion-icon/ion-searchbar.directive.ts
@@ -26,11 +26,11 @@ export class IonSearchbarDirective extends IconReplacer {
replace() {
this.replaceIcon(this.host.querySelector('.searchbar-input-container'), {
- name: SCIcon`search`,
+ name: SCIcon.search,
size: 24,
});
this.replaceIcon(this.host.querySelector('.searchbar-clear-button'), {
- name: SCIcon`close`,
+ name: SCIcon.close,
size: 24,
});
}
diff --git a/frontend/app/src/config/profile-page-sections.ts b/frontend/app/src/config/profile-page-sections.ts
index 37e80000..6b5f9845 100644
--- a/frontend/app/src/config/profile-page-sections.ts
+++ b/frontend/app/src/config/profile-page-sections.ts
@@ -62,7 +62,7 @@ export const profilePageSections: SCSection[] = [
links: [
{
name: 'Favorites',
- icon: SCIcon`grade`,
+ icon: SCIcon.grade,
link: ['/favorites'],
translations: {
de: {
@@ -73,7 +73,7 @@ export const profilePageSections: SCSection[] = [
},
{
name: 'Schedule',
- icon: SCIcon`calendar_today`,
+ icon: SCIcon.calendar_today,
link: ['/schedule'],
translations: {
de: {
@@ -84,7 +84,7 @@ export const profilePageSections: SCSection[] = [
},
{
name: 'Course Catalog',
- icon: SCIcon`inventory_2`,
+ icon: SCIcon.inventory_2,
link: ['/catalog'],
translations: {
de: {
@@ -95,7 +95,7 @@ export const profilePageSections: SCSection[] = [
},
{
name: 'Settings',
- icon: SCIcon`settings`,
+ icon: SCIcon.settings,
link: ['/settings'],
translations: {
de: {
@@ -106,7 +106,7 @@ export const profilePageSections: SCSection[] = [
},
{
name: 'Feedback',
- icon: SCIcon`rate_review`,
+ icon: SCIcon.rate_review,
link: ['/feedback'],
translations: {
de: {
@@ -117,7 +117,7 @@ export const profilePageSections: SCSection[] = [
},
{
name: 'About',
- icon: SCIcon`info`,
+ icon: SCIcon.info,
link: ['/about'],
translations: {
de: {
@@ -140,7 +140,7 @@ export const profilePageSections: SCSection[] = [
links: [
{
name: 'Assessments',
- icon: SCIcon`fact_check`,
+ icon: SCIcon.fact_check,
link: ['/assessments'],
needsAuth: true,
translations: {
@@ -164,7 +164,7 @@ export const profilePageSections: SCSection[] = [
links: [
{
name: 'Library Catalog',
- icon: SCIcon`local_library`,
+ icon: SCIcon.local_library,
link: ['/hebis-search'],
translations: {
de: {
@@ -175,7 +175,7 @@ export const profilePageSections: SCSection[] = [
},
{
name: 'Library Account',
- icon: SCIcon`badge`,
+ icon: SCIcon.badge,
needsAuth: true,
link: ['/library-account/profile'],
translations: {
@@ -187,7 +187,7 @@ export const profilePageSections: SCSection[] = [
},
{
name: 'Orders & Reservations',
- icon: SCIcon`collections_bookmark`,
+ icon: SCIcon.collections_bookmark,
needsAuth: true,
link: ['/library-account/holds'],
translations: {
@@ -199,7 +199,7 @@ export const profilePageSections: SCSection[] = [
},
{
name: 'Checked out items',
- icon: SCIcon`library_books`,
+ icon: SCIcon.library_books,
needsAuth: true,
link: ['/library-account/checked-out'],
translations: {
@@ -211,7 +211,7 @@ export const profilePageSections: SCSection[] = [
},
{
name: 'Fines',
- icon: SCIcon`request_page`,
+ icon: SCIcon.request_page,
needsAuth: true,
link: ['/library-account/fines'],
translations: {