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 ADDED
@@ -0,0 +1,9 @@
1
+ /// <reference path="./node_modules/socket-function/index.d.ts" />
2
+
3
+ export { };
4
+
5
+ declare global {
6
+ interface SerializedModule {
7
+ originalSource?: string;
8
+ }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.175.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.122.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",
@@ -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 prev = document.activeElement;
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 (prev !== document.activeElement) {
962
- debugger;
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: I'm not sure if updating textContent is better or worse than creating a new Text node?
1863
- // As in, should we use `prevNode.replaceWith(document.createTextNode(result))`?
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, SerializedModule, setRequireBootRequire } from "socket-function/require/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
- void clientWatcher.waitForTriggerFinished()?.finally(callback);
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
- let isAmbientEvent = (
81
- e.target === document.body
82
- // Some elements are commonly selected, but shouldn't handling key events
83
- || (e.target as HTMLElement).tagName === "BUTTON"
84
- || (e.target as HTMLElement).tagName === "A"
85
- || (e.target as HTMLElement).tagName === "INPUT" && (e.target as HTMLInputElement).type === "checkbox"
86
- //|| (e.target as HTMLElement).tagName === "INPUT" && (e.target as HTMLInputElement).type === "number" && Number.isNaN(+e.key) && !e.key.includes("Arrow")
87
- //|| ["INPUT", "TEXTAREA"].includes((e.target as HTMLElement).tagName)
88
- );
89
- if (e.key === "Escape") {
90
- (document.activeElement as any)?.blur();
91
- }
92
- let keyFull = e.code;
93
- if (keyFull.startsWith("Key")) {
94
- keyFull = keyFull.slice("Key".length);
95
- }
96
- if (e.metaKey) keyFull = "meta+" + keyFull;
97
- if (e.altKey) keyFull = "alt+" + keyFull;
98
- if (e.shiftKey) keyFull = "shift+" + keyFull;
99
- if (e.ctrlKey || e.metaKey) keyFull = "ctrl+" + keyFull;
100
- if (!isAmbientEvent) {
101
- trigger("global+" + keyFull);
102
- } else {
103
- trigger("global+" + keyFull);
104
- trigger(keyFull);
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
- function trigger(keyFull: string) {
108
- keyFull = keyFull.toLowerCase();
109
- let listeners = Array.from(registeredWatches).map(x => {
110
- let props = Querysub.localRead(() => ({ ...x.props }), { allowProxyResults: true });
111
- let match = props.hotkeys?.find(x => {
112
- let hotkey = x.toLowerCase();
113
- if (hotkey.endsWith("+toggle")) {
114
- hotkey = hotkey.slice(0, -"+toggle".length);
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
- if (match.includes("+toggle")) {
140
- function onKeyUp() {
141
- keyUpListener.delete(onKeyUp);
142
- listener.element?.click();
143
- }
144
- keyUpListener.add(onKeyUp);
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
- if (Querysub.localRead(() => props.immediateRepeat)) {
147
- insideAnims.add(listener);
148
- let dead = false;
149
- function onKeyUp() {
150
- keyUpListener.delete(onKeyUp);
151
- insideAnims.delete(listener);
152
- dead = true;
153
- }
154
- keyUpListener.add(onKeyUp);
155
- let prevFrame = Date.now();
156
- function onFrame() {
157
- if (dead) {
158
- animationDuration = 0;
159
- return;
160
- }
161
- let now = Date.now();
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
+ */