Compare commits

...

5 Commits

Author SHA1 Message Date
3fd5c5ed57 Added sync function runner
All checks were successful
Build / Build NPM Project (push) Successful in 42s
Build / Tag Version (push) Successful in 7s
Build / Publish Documentation (push) Successful in 41s
2025-05-06 19:52:32 -04:00
2e4559d805 Added async function runner
All checks were successful
Build / Build NPM Project (push) Successful in 37s
Build / Tag Version (push) Successful in 8s
Build / Publish Documentation (push) Successful in 34s
2025-05-06 16:09:24 -04:00
95f8d5762c Fixed import error
All checks were successful
Build / Build NPM Project (push) Successful in 57s
Build / Tag Version (push) Successful in 13s
Build / Publish Documentation (push) Successful in 48s
2025-05-06 16:01:57 -04:00
3bc82fab45 - Fixed cache.addAll()
Some checks failed
Build / Build NPM Project (push) Failing after 34s
Build / Publish Documentation (push) Has been skipped
Build / Tag Version (push) Has been skipped
- Renamed jwtDecode to decodeJWT to match conventions
- Added testCondition to search
2025-05-06 15:59:08 -04:00
6b15848896 Fixed defaulting date in date formatter
All checks were successful
Build / Build NPM Project (push) Successful in 1m15s
Build / Tag Version (push) Successful in 16s
Build / Publish Documentation (push) Successful in 49s
2025-04-30 11:58:03 -04:00
8 changed files with 151 additions and 44 deletions

View File

@ -1,15 +1,104 @@
<!Doctype html>
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ztimson/utils sandbox</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>About Us | OurTrainingRoom</title>
<style>
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #fdfdfd;
color: #333;
line-height: 1.6;
}
header {
background: #004080;
color: #fff;
padding: 2rem 1rem;
text-align: center;
}
header h1 {
margin: 0;
font-size: 2.5rem;
}
main {
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
section {
margin-bottom: 2rem;
}
h2 {
color: #004080;
margin-bottom: 0.5rem;
}
ul {
padding-left: 1.25rem;
}
footer {
text-align: center;
font-size: 0.9rem;
color: #777;
padding: 2rem 1rem;
background: #f1f1f1;
margin-top: 4rem;
}
</style>
</head>
<body>
<script type="module">
import {PathEvent} from './dist/index.mjs';
<header>
<h1>About Us</h1>
<p>Empowering Learning Through Innovation</p>
</header>
console.log(PathEvent.filter(['payments/ztimson:cr', 'logs/momentum:c', 'data/Testing:r'], 'data'));
console.log(PathEvent.filter(['data/Submissions/Test:r'], 'data/Submissions/Test/test.html'));
</script>
<main>
<section>
<p>
E-learning has evolved significantly since its inception. Today, there's a shift towards
blended learning services, integrating online activities with practical, real-world applications.
</p>
</section>
<section>
<h2>What We Do</h2>
<p>At <strong>OurTrainingRoom.com</strong>, we specialize in content management and professional development training tailored for:</p>
<ul>
<li>School Boards</li>
<li>Municipalities</li>
<li>Hospitals</li>
<li>Large Corporations</li>
</ul>
</section>
<section>
<h2>Our Roots</h2>
<p>
Our parent company, <strong>The Auxilium Group</strong>, is a leader in online data management.
The formation of OurTrainingRoom.com was a natural progression to deliver state-of-the-art front-end e-learning programs.
</p>
</section>
<section>
<h2>Our Approach</h2>
<p>
Built on principles of quality and continuous improvement, our diverse delivery range continues to grow.
We set new trends by enhancing our existing products and attentively listening to our clients and their employees.
This unique approach has solidified our position in the industry, making a substantial impact for our clients.
</p>
</section>
<section>
<h2>Have a Question?</h2>
<p>
We value your inquiries and are here to assist you. Please reach out with any questions or feedback.
</p>
</section>
</main>
<footer>
&copy; 2025 OurTrainingRoom.com. All rights reserved.
</footer>
</body>
</html>

View File

@ -1,6 +1,6 @@
{
"name": "@ztimson/utils",
"version": "0.23.22",
"version": "0.24.2",
"description": "Utility library",
"author": "Zak Timson",
"license": "MIT",

View File

@ -22,7 +22,6 @@ export class Cache<K extends string | number | symbol, T> {
/**
* Create new cache
*
* @param {keyof T} key Default property to use as primary key
* @param options
*/
@ -56,7 +55,6 @@ export class Cache<K extends string | number | symbol, T> {
/**
* Get all cached items
*
* @return {T[]} Array of items
*/
all(): T[] {
@ -65,7 +63,6 @@ export class Cache<K extends string | number | symbol, T> {
/**
* Add a new item to the cache. Like set, but finds key automatically
*
* @param {T} value Item to add to cache
* @param {number | undefined} ttl Override default expiry
* @return {this}
@ -78,12 +75,12 @@ export class Cache<K extends string | number | symbol, T> {
/**
* Add several rows to the cache
*
* @param {T[]} rows Several items that will be cached using the default key
* @param complete Mark cache as complete & reliable, defaults to true
* @return {this}
*/
addAll(rows: T[], complete = true): this {
this.clear();
rows.forEach(r => this.add(r));
this.complete = complete;
return this;
@ -98,7 +95,6 @@ export class Cache<K extends string | number | symbol, T> {
/**
* Delete an item from the cache
*
* @param {K} key Item's primary key
*/
delete(key: K) {
@ -126,7 +122,6 @@ export class Cache<K extends string | number | symbol, T> {
/**
* Get a list of cached keys
*
* @return {K[]} Array of keys
*/
keys(): K[] {
@ -135,7 +130,6 @@ export class Cache<K extends string | number | symbol, T> {
/**
* Get map of cached items
*
* @return {Record<K, T>}
*/
map(): Record<K, T> {
@ -144,7 +138,6 @@ export class Cache<K extends string | number | symbol, T> {
/**
* Add an item to the cache manually specifying the key
*
* @param {K} key Key item will be cached under
* @param {T} value Item to cache
* @param {number | undefined} ttl Override default expiry in seconds
@ -163,7 +156,6 @@ export class Cache<K extends string | number | symbol, T> {
/**
* Get all cached items
*
* @return {T[]} Array of items
*/
values = this.all();

View File

@ -6,7 +6,7 @@ import {JSONAttemptParse} from './objects.ts';
* @param {string} token JWT to decode
* @return {unknown} JWT payload
*/
export function jwtDecode<T>(token: string): T {
export function decodeJwt<T>(token: string): T {
const base64 = token.split('.')[1]
.replace(/-/g, '+').replace(/_/g, '/');
return <T>JSONAttemptParse(decodeURIComponent(atob(base64).split('').map(function(c) {

View File

@ -1,6 +1,18 @@
import {PathEvent} from './path-events.ts';
import {md5} from './string';
/**
* Run a stringified function with arguments asynchronously
* @param {object} args Map of key/value arguments
* @param {string} fn Function as string
* @param {boolean} async Run with async (returns a promise)
* @return {T | Promise<T>} Function return result
*/
export function fn<T>(args: object, fn: string, async: boolean = false): T {
const keys = Object.keys(args);
return new Function(...keys, `return (${async ? 'async ' : ''}(${keys.join(',')}) => { ${fn} })(${keys.join(',')})`)(...keys.map(k => (<any>args)[k]));
}
/**
* Get profile image from Gravatar
*

View File

@ -109,7 +109,6 @@ export function encodeQuery(data: any): string {
).join('&');
}
/**
* Recursively flatten a nested object, while maintaining key structure
*

View File

@ -1,4 +1,4 @@
import {dotNotation, JSONAttemptParse} from './objects';
import {dotNotation, JSONAttemptParse} from './objects.ts';
export function search(rows: any[], search: string, regex?: boolean, transform: Function = (r: any) => r) {
if(!rows) return [];
@ -12,27 +12,42 @@ export function search(rows: any[], search: string, regex?: boolean, transform:
try { return RegExp(search, 'gm').test(v.toString()); }
catch { return false; }
}).length
} else {
return testCondition(search, r);
}
// Make sure at least one OR passes
const or = search.split('||').map(p => p.trim()).filter(p => !!p);
});
}
export function testCondition(condition: string, row: any) {
const evalBoolean = (a: any, op: string, b: any): boolean => {
switch(op) {
case '=':
case '==': return a == b;
case '!=': return a != b;
case '>': return a > b;
case '>=': return a >= b;
case '<': return a < b;
case '<=': return a <= b;
default: return false;
}
}
const or = condition.split('||').map(p => p.trim()).filter(p => !!p);
return -1 != or.findIndex(p => {
// Make sure all ANDs pass
const and = p.split('&&').map(p => p.trim()).filter(p => !!p);
return and.filter(p => {
// Boolean operator
const prop = /(\w+)\s*(==?|!=|>=|>|<=|<)\s*(\w+)/g.exec(p);
const prop = /(\S+)\s*(==?|!=|>=|>|<=|<)\s*(\S+)/g.exec(p);
if(prop) {
const a = JSON.stringify(JSONAttemptParse(dotNotation<any>(value, prop[1])));
const operator = prop[2] == '=' ? '==' : prop[2];
const b = JSON.stringify(JSONAttemptParse(prop[3]));
return eval(`${a} ${operator} ${b}`);
const key = Object.keys(row).find(k => k.toLowerCase() == prop[1].toLowerCase());
return evalBoolean(dotNotation<any>(row, key || prop[1]), prop[2], JSONAttemptParse(prop[3]));
}
// Case-sensitive
const v = Object.values(value).join('');
if(/[A-Z]/g.test(search)) return v.includes(p);
const v = Object.values(row).map(v => typeof v == 'object' && v != null ? JSON.stringify(v) : v).join('');
if(/[A-Z]/g.test(condition)) return v.includes(p);
// Case-insensitive
return v.toLowerCase().includes(p);
}).length == and.length;
})
});
}

View File

@ -95,7 +95,7 @@ export function formatDate(format = 'YYYY-MM-DD H:mm', date: Date | number | str
return (offset > 0 ? '-' : '') + `${hours}:${minutes.toString().padStart(2, '0')}`;
}
if(typeof date == 'number' || typeof date == 'string') date = new Date(date);
if(typeof date == 'number' || typeof date == 'string' || date == null) date = new Date(date);
// Handle timezones
let t!: [string, number];