querysub 0.175.0 → 0.177.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +9 -0
- package/package.json +2 -2
- package/src/4-dom/qreact.tsx +11 -6
- package/src/4-querysub/Querysub.ts +19 -3
- package/src/library-components/Button.tsx +89 -86
- package/src/library-components/URLParam.ts +3 -0
- package/src/niceStringify.ts +83 -0
package/index.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.177.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
25
25
|
"pako": "^2.1.0",
|
|
26
26
|
"preact": "^10.11.3",
|
|
27
|
-
"socket-function": "^0.
|
|
27
|
+
"socket-function": "^0.124.0",
|
|
28
28
|
"terser": "^5.31.0",
|
|
29
29
|
"typesafecss": "^0.15.0",
|
|
30
30
|
"yaml": "^2.5.0",
|
package/src/4-dom/qreact.tsx
CHANGED
|
@@ -96,6 +96,9 @@ export namespace qreact {
|
|
|
96
96
|
export type Element = preact.JSX.Element;
|
|
97
97
|
export type HTMLAttributes<T extends EventTarget = EventTarget> = preact.JSX.HTMLAttributes<T>;
|
|
98
98
|
}
|
|
99
|
+
export namespace JSXInternal {
|
|
100
|
+
export type TargetedInputEvent<T extends EventTarget = EventTarget> = preact.JSX.TargetedInputEvent<T>;
|
|
101
|
+
}
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
|
|
@@ -911,7 +914,7 @@ class QRenderClass {
|
|
|
911
914
|
}
|
|
912
915
|
|
|
913
916
|
function insertInAnchor(anchor: Anchor, node: DOMNode) {
|
|
914
|
-
let
|
|
917
|
+
//let prevFocused = document.activeElement;
|
|
915
918
|
if (!anchor) {
|
|
916
919
|
nextRootDOMNodes.push(node);
|
|
917
920
|
return;
|
|
@@ -958,9 +961,9 @@ class QRenderClass {
|
|
|
958
961
|
}
|
|
959
962
|
|
|
960
963
|
anchor.prev.after(node);
|
|
961
|
-
if (
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
+
// if (prevFocused !== document.activeElement) {
|
|
965
|
+
// debugger;
|
|
966
|
+
// }
|
|
964
967
|
}
|
|
965
968
|
|
|
966
969
|
let isParentSVG = !!atomic(self.data().isParentSVG);
|
|
@@ -1859,9 +1862,11 @@ function updateDOMNodeFields(domNode: DOMNode, vNode: VirtualDOM, prevVNode: Vir
|
|
|
1859
1862
|
updateField(key);
|
|
1860
1863
|
}
|
|
1861
1864
|
} else {
|
|
1862
|
-
// NOTE:
|
|
1863
|
-
//
|
|
1865
|
+
// NOTE: By setting textContent we don't create a new Text node. This helps
|
|
1866
|
+
// contentEditable usages distinguish between our internal nodes, and ones
|
|
1867
|
+
// generated by contentEditable.
|
|
1864
1868
|
(domNode as any as Text).textContent = String(vNode);
|
|
1869
|
+
//domNode.replaceWith(document.createTextNode(String(vNode)));
|
|
1865
1870
|
}
|
|
1866
1871
|
}
|
|
1867
1872
|
|
|
@@ -10,7 +10,7 @@ import { isNode, isNodeTrue, timeInMinute } from "socket-function/src/misc";
|
|
|
10
10
|
|
|
11
11
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
12
12
|
import { isHotReloading, watchFilesAndTriggerHotReloading } from "socket-function/hot/HotReloadController";
|
|
13
|
-
import { RequireController,
|
|
13
|
+
import { RequireController, setRequireBootRequire } from "socket-function/require/RequireController";
|
|
14
14
|
import { cache, cacheLimited, lazy } from "socket-function/src/caching";
|
|
15
15
|
import { getOwnMachineId, getThreadKeyCert, verifyMachineIdForPublicKey } from "../-a-auth/certs";
|
|
16
16
|
import { getSNICerts, publishMachineARecords } from "../-e-certs/EdgeCertController";
|
|
@@ -390,8 +390,9 @@ export class Querysub {
|
|
|
390
390
|
|
|
391
391
|
public static afterAllRendersFinished(callback: () => void) {
|
|
392
392
|
// onCommitFinished prevents duplicates, as well as only running when we are actually done
|
|
393
|
-
Querysub.onCommitFinished(() => {
|
|
394
|
-
|
|
393
|
+
Querysub.onCommitFinished(async () => {
|
|
394
|
+
await clientWatcher.waitForTriggerFinished();
|
|
395
|
+
callback();
|
|
395
396
|
});
|
|
396
397
|
}
|
|
397
398
|
|
|
@@ -599,6 +600,21 @@ export class Querysub {
|
|
|
599
600
|
|
|
600
601
|
await this.addSourceMapCheck(config);
|
|
601
602
|
|
|
603
|
+
const fs = await import("fs");
|
|
604
|
+
|
|
605
|
+
RequireController.addMapGetModules(async (result, args) => {
|
|
606
|
+
await Promise.all(Object.values(result.modules).map(async (mod) => {
|
|
607
|
+
if (!mod.allowclient) return;
|
|
608
|
+
if (mod.serveronly) return;
|
|
609
|
+
// NOTE: This is used for extractType... which is very useful. And... giving the source
|
|
610
|
+
// isn't that big of a deal anyways...
|
|
611
|
+
try {
|
|
612
|
+
mod.originalSource = await fs.promises.readFile(mod.filename, "utf8");
|
|
613
|
+
} catch { }
|
|
614
|
+
}));
|
|
615
|
+
return result;
|
|
616
|
+
});
|
|
617
|
+
|
|
602
618
|
SocketFunction.expose(RequireController);
|
|
603
619
|
setRequireBootRequire(config.rootPath);
|
|
604
620
|
|
|
@@ -69,106 +69,109 @@ type Watcher = {
|
|
|
69
69
|
let registeredWatches = new Set<Watcher>();
|
|
70
70
|
|
|
71
71
|
if (!isNode()) {
|
|
72
|
-
let insideAnims = new Set<Watcher>();
|
|
73
|
-
let keyUpListener = new Set<() => void>();
|
|
74
72
|
document.addEventListener("keyup", e => {
|
|
75
73
|
for (let listener of keyUpListener) {
|
|
76
74
|
listener();
|
|
77
75
|
}
|
|
78
76
|
});
|
|
79
77
|
document.addEventListener("keydown", e => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
78
|
+
triggerKeyDown(e, false);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
let insideAnims = new Set<Watcher>();
|
|
82
|
+
let keyUpListener = new Set<() => void>();
|
|
83
|
+
export function triggerKeyDown(e: KeyboardEvent, forceAmbient = true) {
|
|
84
|
+
let isAmbientEvent = (
|
|
85
|
+
e.target === document.body
|
|
86
|
+
// Some elements are commonly selected, but shouldn't handling key events
|
|
87
|
+
|| (e.target as HTMLElement).tagName === "BUTTON"
|
|
88
|
+
|| (e.target as HTMLElement).tagName === "A"
|
|
89
|
+
|| (e.target as HTMLElement).tagName === "INPUT" && (e.target as HTMLInputElement).type === "checkbox"
|
|
90
|
+
//|| (e.target as HTMLElement).tagName === "INPUT" && (e.target as HTMLInputElement).type === "number" && Number.isNaN(+e.key) && !e.key.includes("Arrow")
|
|
91
|
+
//|| ["INPUT", "TEXTAREA"].includes((e.target as HTMLElement).tagName)
|
|
92
|
+
) || forceAmbient;
|
|
93
|
+
if (e.key === "Escape") {
|
|
94
|
+
(document.activeElement as any)?.blur();
|
|
95
|
+
}
|
|
96
|
+
let keyFull = e.code;
|
|
97
|
+
if (keyFull.startsWith("Key")) {
|
|
98
|
+
keyFull = keyFull.slice("Key".length);
|
|
99
|
+
}
|
|
100
|
+
if (e.metaKey) keyFull = "meta+" + keyFull;
|
|
101
|
+
if (e.altKey) keyFull = "alt+" + keyFull;
|
|
102
|
+
if (e.shiftKey) keyFull = "shift+" + keyFull;
|
|
103
|
+
if (e.ctrlKey || e.metaKey) keyFull = "ctrl+" + keyFull;
|
|
104
|
+
if (!isAmbientEvent) {
|
|
105
|
+
trigger("global+" + keyFull);
|
|
106
|
+
} else {
|
|
107
|
+
trigger("global+" + keyFull);
|
|
108
|
+
trigger(keyFull);
|
|
109
|
+
}
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
return hotkey === keyFull;
|
|
117
|
-
}) || "";
|
|
118
|
-
let hotkeyPriority = match && (
|
|
119
|
-
props.hotkeyPriority
|
|
120
|
-
?? Querysub.localRead(() => qreact.getAncestorProps(ButtonHotkeyRegion, x)?.hotkeyPriority, { allowProxyResults: true })
|
|
121
|
-
?? Number.MIN_SAFE_INTEGER
|
|
122
|
-
) || Number.MIN_SAFE_INTEGER;
|
|
123
|
-
return { match, listener: x, props, hotkeyPriority };
|
|
124
|
-
}).filter(x => x.match);
|
|
125
|
-
if (!listeners.length) return;
|
|
126
|
-
console.log(`Triggering hotkey ${JSON.stringify(keyFull)} for ${listeners.length} listeners`);
|
|
127
|
-
e.preventDefault();
|
|
128
|
-
e.stopPropagation();
|
|
129
|
-
let maxPriority = Array.from(listeners).reduce((a, b) => Math.max(a, b.hotkeyPriority), Number.MIN_SAFE_INTEGER);
|
|
130
|
-
listeners = listeners.filter(x => x.hotkeyPriority === maxPriority);
|
|
131
|
-
if (listeners.length === 0) return;
|
|
132
|
-
listeners = listeners.filter((x, index) => x.props.callEvenIfRedundant || index === listeners.length - 1);
|
|
133
|
-
for (let { match, listener, props } of listeners) {
|
|
134
|
-
if (insideAnims.has(listener)) continue;
|
|
135
|
-
listener.element?.click();
|
|
136
|
-
if (!keyFull.includes("global+")) {
|
|
137
|
-
listener.element?.focus();
|
|
111
|
+
function trigger(keyFull: string) {
|
|
112
|
+
keyFull = keyFull.toLowerCase();
|
|
113
|
+
let listeners = Array.from(registeredWatches).map(x => {
|
|
114
|
+
let props = Querysub.localRead(() => ({ ...x.props }), { allowProxyResults: true });
|
|
115
|
+
let match = props.hotkeys?.find(x => {
|
|
116
|
+
let hotkey = x.toLowerCase();
|
|
117
|
+
if (hotkey.endsWith("+toggle")) {
|
|
118
|
+
hotkey = hotkey.slice(0, -"+toggle".length);
|
|
138
119
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
120
|
+
return hotkey === keyFull;
|
|
121
|
+
}) || "";
|
|
122
|
+
let hotkeyPriority = match && (
|
|
123
|
+
props.hotkeyPriority
|
|
124
|
+
?? Querysub.localRead(() => qreact.getAncestorProps(ButtonHotkeyRegion, x)?.hotkeyPriority, { allowProxyResults: true })
|
|
125
|
+
?? Number.MIN_SAFE_INTEGER
|
|
126
|
+
) || Number.MIN_SAFE_INTEGER;
|
|
127
|
+
return { match, listener: x, props, hotkeyPriority };
|
|
128
|
+
}).filter(x => x.match);
|
|
129
|
+
if (!listeners.length) return;
|
|
130
|
+
console.log(`Triggering hotkey ${JSON.stringify(keyFull)} for ${listeners.length} listeners`);
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
e.stopPropagation();
|
|
133
|
+
let maxPriority = Array.from(listeners).reduce((a, b) => Math.max(a, b.hotkeyPriority), Number.MIN_SAFE_INTEGER);
|
|
134
|
+
listeners = listeners.filter(x => x.hotkeyPriority === maxPriority);
|
|
135
|
+
if (listeners.length === 0) return;
|
|
136
|
+
listeners = listeners.filter((x, index) => x.props.callEvenIfRedundant || index === listeners.length - 1);
|
|
137
|
+
for (let { match, listener, props } of listeners) {
|
|
138
|
+
if (insideAnims.has(listener)) continue;
|
|
139
|
+
listener.element?.click();
|
|
140
|
+
if (!keyFull.includes("global+")) {
|
|
141
|
+
listener.element?.focus();
|
|
142
|
+
}
|
|
143
|
+
if (match.includes("+toggle")) {
|
|
144
|
+
function onKeyUp() {
|
|
145
|
+
keyUpListener.delete(onKeyUp);
|
|
146
|
+
listener.element?.click();
|
|
145
147
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
animationDuration = now - prevFrame;
|
|
163
|
-
prevFrame = now;
|
|
164
|
-
listener.element?.click();
|
|
165
|
-
requestAnimationFrame(onFrame);
|
|
148
|
+
keyUpListener.add(onKeyUp);
|
|
149
|
+
}
|
|
150
|
+
if (Querysub.localRead(() => props.immediateRepeat)) {
|
|
151
|
+
insideAnims.add(listener);
|
|
152
|
+
let dead = false;
|
|
153
|
+
function onKeyUp() {
|
|
154
|
+
keyUpListener.delete(onKeyUp);
|
|
155
|
+
insideAnims.delete(listener);
|
|
156
|
+
dead = true;
|
|
157
|
+
}
|
|
158
|
+
keyUpListener.add(onKeyUp);
|
|
159
|
+
let prevFrame = Date.now();
|
|
160
|
+
function onFrame() {
|
|
161
|
+
if (dead) {
|
|
162
|
+
animationDuration = 0;
|
|
163
|
+
return;
|
|
166
164
|
}
|
|
165
|
+
let now = Date.now();
|
|
166
|
+
animationDuration = now - prevFrame;
|
|
167
|
+
prevFrame = now;
|
|
168
|
+
listener.element?.click();
|
|
167
169
|
requestAnimationFrame(onFrame);
|
|
168
170
|
}
|
|
171
|
+
requestAnimationFrame(onFrame);
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
|
-
}
|
|
174
|
+
}
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
let animationDuration = 0;
|
|
@@ -244,6 +244,9 @@ export function parseSearchString(search: string): { [key: string]: unknown } {
|
|
|
244
244
|
let parts = search.split("&");
|
|
245
245
|
let output: { [key: string]: string | undefined } = Object.create(null);
|
|
246
246
|
for (let part of parts) {
|
|
247
|
+
if (part.endsWith("=")) {
|
|
248
|
+
part = part.slice(0, -1);
|
|
249
|
+
}
|
|
247
250
|
let equalIndex = part.indexOf("=");
|
|
248
251
|
if (equalIndex === -1) {
|
|
249
252
|
output[decodeURIComponent(part)] = undefined;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// true => ""
|
|
2
|
+
// "" => JSON.stringify("")
|
|
3
|
+
|
|
4
|
+
export const niceStringifyTrue = "";
|
|
5
|
+
// Starting/ending with a JSON character means anything string that looks like this
|
|
6
|
+
// will be encoded like: `"{Nan`, and it is impossible for an object to serialize to look like this.
|
|
7
|
+
export const niceStringifyNan = `{NaN}`;
|
|
8
|
+
export const niceStringifyUndefined = `{Undefined}`;
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
// BUG: This is actually broken for hex strings. Hex strings may sometimes be entirely numbers,
|
|
12
|
+
// which means they will randomly change type.
|
|
13
|
+
function looksLikeJSON(str: string) {
|
|
14
|
+
return (
|
|
15
|
+
str === "null"
|
|
16
|
+
|| str === "true"
|
|
17
|
+
|| str === "false"
|
|
18
|
+
|| str[0] === `"` && str[str.length - 1] === `"`
|
|
19
|
+
|| str[0] === `[` && str[str.length - 1] === `]`
|
|
20
|
+
|| str[0] === `{` && str[str.length - 1] === `}`
|
|
21
|
+
|| (48 <= str.charCodeAt(0) && str.charCodeAt(0) <= 57)
|
|
22
|
+
|| str.length > 1 && str[0] === "-" && (48 <= str.charCodeAt(1) && str.charCodeAt(1) <= 57)
|
|
23
|
+
|| str === niceStringifyTrue
|
|
24
|
+
|| str === niceStringifyUndefined
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function niceStringify(value: unknown): string {
|
|
29
|
+
if (value === undefined) {
|
|
30
|
+
return niceStringifyUndefined;
|
|
31
|
+
}
|
|
32
|
+
if (value === true) return niceStringifyTrue;
|
|
33
|
+
if (Number.isNaN(value)) return niceStringifyNan;
|
|
34
|
+
|
|
35
|
+
// Any strings that don't look like JSON, don't need to encoded as JSON, and can instead
|
|
36
|
+
// just be stored as strings.
|
|
37
|
+
if (typeof value === "string" && !looksLikeJSON(value)) {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
let str = JSON.stringify(value);
|
|
43
|
+
if (typeof value !== "object") {
|
|
44
|
+
let testParse = niceParse(str);
|
|
45
|
+
if (testParse !== value) {
|
|
46
|
+
console.log(`niceStringify did not reverse correctly. Should have received ${JSON.stringify(value)}, did received ${JSON.stringify(testParse)}`);
|
|
47
|
+
debugger;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return str;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function niceParse(str: string | undefined, noSpecialTrue = false): unknown {
|
|
55
|
+
if (str === undefined) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
if (str === niceStringifyTrue && !noSpecialTrue) return true;
|
|
59
|
+
if (str === niceStringifyNan) return Number.NaN;
|
|
60
|
+
if (str === niceStringifyUndefined) return undefined;
|
|
61
|
+
if (str === "") return str;
|
|
62
|
+
|
|
63
|
+
if (looksLikeJSON(str)) {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(str);
|
|
66
|
+
} catch { }
|
|
67
|
+
}
|
|
68
|
+
return str;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/*
|
|
72
|
+
|
|
73
|
+
function setFromUrlValue(key: string, valueJSON: string) {
|
|
74
|
+
if(isMaybeJSON(valueJSON)) {
|
|
75
|
+
try {
|
|
76
|
+
values[key] = JSON.parse(valueJSON);
|
|
77
|
+
return;
|
|
78
|
+
} catch { }
|
|
79
|
+
}
|
|
80
|
+
// Always set it, if it isn't JSON, just assume it is raw text.
|
|
81
|
+
values[key] = valueJSON;
|
|
82
|
+
}
|
|
83
|
+
*/
|