PlugOS refactor and other tweaks (#631)

* Prep for in-process plug loading (e.g. for CF workers, Deno Deploy)
* Prototype of fixed in-process loading plugs
* Fix: buttons not to scroll with content
* Better positioning of modal especially on mobile
* Move query caching outside query
* Fix annoying mouse behavior when filter box appears
* Page navigator search tweaks
This commit is contained in:
Zef Hemel
2024-01-15 16:43:12 +01:00
committed by GitHub
parent a9eb252658
commit a2dbf7b3db
65 changed files with 591 additions and 617 deletions
+7 -7
View File
@@ -37,24 +37,24 @@ export class PromiseQueue {
resolve: (value: any) => void;
reject: (error: any) => void;
}[] = [];
private running = false;
private processing = false;
runInQueue(fn: () => Promise<any>): Promise<any> {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
if (!this.running) {
this.run();
if (!this.processing) {
this.process();
}
});
}
private async run(): Promise<void> {
private async process(): Promise<void> {
if (this.queue.length === 0) {
this.running = false;
this.processing = false;
return;
}
this.running = true;
this.processing = true;
const { fn, resolve, reject } = this.queue.shift()!;
try {
@@ -64,7 +64,7 @@ export class PromiseQueue {
reject(error);
}
this.run(); // Continue processing the next promise in the queue
this.process(); // Continue processing the next promise in the queue
}
}
+3
View File
@@ -7,6 +7,7 @@ import {
replaceNodesMatchingAsync,
traverseTreeAsync,
} from "$sb/lib/tree.ts";
import { expandPropertyNames } from "$sb/lib/json.ts";
export type FrontMatter = { tags?: string[] } & Record<string, any>;
@@ -116,6 +117,8 @@ export async function extractFrontmatter(
data.tags = [...new Set([...tags.map((t) => t.replace(/^#/, ""))])];
// console.log("Extracted tags", data.tags);
// Expand property names (e.g. "foo.bar" => { foo: { bar: true } })
data = expandPropertyNames(data);
return data;
}
+23
View File
@@ -0,0 +1,23 @@
import { sleep } from "$sb/lib/async.ts";
import { assertEquals } from "../../test_deps.ts";
import { LimitedMap } from "./limited_map.ts";
Deno.test("limited map", async () => {
const mp = new LimitedMap<string>(3);
mp.set("a", "a");
mp.set("b", "b", 5);
mp.set("c", "c");
assertEquals(mp.get("a"), "a");
assertEquals(mp.get("b"), "b");
assertEquals(mp.get("c"), "c");
// Drops the first key
mp.set("d", "d");
// console.log(mp.toJSON());
assertEquals(mp.get("a"), undefined);
await sleep(10);
// "b" should have been dropped
assertEquals(mp.get("b"), undefined);
assertEquals(mp.get("c"), "c");
console.log(mp.toJSON());
});
+72
View File
@@ -0,0 +1,72 @@
type LimitedMapRecord<V> = {
value: V;
la: number;
expTimer?: number;
};
export class LimitedMap<V> {
private map: Map<string, LimitedMapRecord<V>>;
constructor(
private maxSize: number,
initialJson: Record<string, LimitedMapRecord<V>> = {},
) {
this.map = new Map(Object.entries(initialJson));
}
/**
* @param key
* @param value
* @param ttl time to live (in ms)
*/
set(key: string, value: V, ttl?: number) {
const entry: LimitedMapRecord<V> = { value, la: Date.now() };
if (ttl) {
const existingEntry = this.map.get(key);
if (existingEntry?.expTimer) {
clearTimeout(existingEntry.expTimer);
}
entry.expTimer = setTimeout(() => {
this.map.delete(key);
}, ttl);
}
if (this.map.size >= this.maxSize) {
// Remove the oldest key before adding a new one
const oldestKey = this.getOldestKey();
this.map.delete(oldestKey!);
}
this.map.set(key, entry);
}
get(key: string): V | undefined {
const entry = this.map.get(key);
if (entry) {
// Update the last accessed timestamp
entry.la = Date.now();
return entry.value;
}
return undefined;
}
remove(key: string) {
this.map.delete(key);
}
toJSON() {
return Object.fromEntries(this.map.entries());
}
private getOldestKey(): string | undefined {
let oldestKey: string | undefined;
let oldestTimestamp: number | undefined;
for (const [key, entry] of this.map.entries()) {
if (!oldestTimestamp || entry.la < oldestTimestamp) {
oldestKey = key;
oldestTimestamp = entry.la;
}
}
return oldestKey;
}
}
+17
View File
@@ -0,0 +1,17 @@
import { sleep } from "$sb/lib/async.ts";
import { ttlCache } from "$sb/lib/memory_cache.ts";
import { assertEquals } from "../../test_deps.ts";
Deno.test("Memory cache", async () => {
let calls = 0;
async function expensiveFunction(key: string) {
calls++;
await sleep(1);
return key;
}
assertEquals("key", await ttlCache("key", expensiveFunction, 0.01));
assertEquals(1, calls);
assertEquals("key", await ttlCache("key", expensiveFunction, 0.01));
assertEquals(1, calls);
await sleep(10);
});
+21
View File
@@ -0,0 +1,21 @@
import { LimitedMap } from "$sb/lib/limited_map.ts";
const cache = new LimitedMap<any>(50);
export async function ttlCache<K, V>(
key: K,
fn: (key: K) => Promise<V>,
ttlSecs?: number,
): Promise<V> {
if (!ttlSecs) {
return fn(key);
}
const serializedKey = JSON.stringify(key);
const cached = cache.get(serializedKey);
if (cached) {
return cached;
}
const result = await fn(key);
cache.set(serializedKey, result, ttlSecs * 1000);
return result;
}