jsmdcui 0.4.0 → 0.6.3

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/src/cui/rpc.mjs CHANGED
@@ -9,6 +9,208 @@ let apilist = new Map()
9
9
 
10
10
  export const jss = JSON.stringify
11
11
 
12
+ function parseDollarIdentity(input, { selector = false } = {})
13
+ {
14
+ const text = String(input ?? "").trim();
15
+ const match = text.match(/^([A-Za-z_][\w:-]*)?(?:#([A-Za-z_][\w:-]*))?((?:\.[A-Za-z_][\w:-]*)*)$/);
16
+ if (!match || (!match[1] && !match[2] && !match[3])) return null;
17
+ if (!selector && !match[1]) return null;
18
+ return {
19
+ tag: match[1] || null,
20
+ id: match[2] || null,
21
+ classes: match[3] ? match[3].slice(1).split(".") : [],
22
+ };
23
+ }
24
+
25
+ function matchesDollarIdentity(identity, selector)
26
+ {
27
+ if (selector.tag && identity.tag !== selector.tag) return false;
28
+ if (selector.id && identity.id !== selector.id) return false;
29
+ return selector.classes.every(name => identity.classes.includes(name));
30
+ }
31
+
32
+ function findMarkdownCodeElement(documentObject, selector)
33
+ {
34
+ for (const code of documentObject?.querySelectorAll?.("pre > code") ?? []) {
35
+ for (const className of code.classList ?? []) {
36
+ if (!className.startsWith("language-")) continue;
37
+ const identity = parseDollarIdentity(className.slice("language-".length));
38
+ if (identity && matchesDollarIdentity(identity, selector)) return code;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+
44
+ function findWebDollarElement(documentObject, selectorText, selector)
45
+ {
46
+ try {
47
+ const direct = documentObject?.querySelector?.(String(selectorText));
48
+ if (direct) return direct;
49
+ } catch {}
50
+
51
+ for (const element of documentObject?.querySelectorAll?.("[data-mdcui-tag]") ?? []) {
52
+ const identity = {
53
+ tag: element.getAttribute?.("data-mdcui-tag") || null,
54
+ id: element.id || null,
55
+ classes: [...(element.classList ?? [])],
56
+ };
57
+ if (matchesDollarIdentity(identity, selector)) return element;
58
+ }
59
+
60
+ return findMarkdownCodeElement(documentObject, selector);
61
+ }
62
+
63
+ function webDollarValue(element)
64
+ {
65
+ if (element && "value" in element) return String(element.value ?? "");
66
+ return String(element?.textContent ?? "").replace(/\n$/, "");
67
+ }
68
+
69
+ function isWebHeading(element)
70
+ {
71
+ return /^h[1-6]$/i.test(String(element?.tagName ?? ""));
72
+ }
73
+
74
+ function firstHeadingTaskList(heading)
75
+ {
76
+ for (let sibling = heading?.nextElementSibling; sibling; sibling = sibling.nextElementSibling) {
77
+ if (isWebHeading(sibling) || String(sibling.tagName ?? "").toLowerCase() === "section")
78
+ break;
79
+ const task = sibling.matches?.("li.task-list-item")
80
+ ? sibling
81
+ : sibling.querySelector?.("li.task-list-item");
82
+ if (!task) continue;
83
+ const list = task.closest?.("ul, ol");
84
+ if (list) return list;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ function directTaskCheckbox(item)
90
+ {
91
+ for (const checkbox of item?.querySelectorAll?.('input[type="checkbox"]') ?? []) {
92
+ if (checkbox.closest?.("li.task-list-item") === item) return checkbox;
93
+ }
94
+ return null;
95
+ }
96
+
97
+ function webTaskItemValue(item, checkbox)
98
+ {
99
+ const label = checkbox?.closest?.("label");
100
+ if (label && label.closest?.("li.task-list-item") === item)
101
+ return String(label.textContent ?? "").trim();
102
+
103
+ const copy = item.cloneNode?.(true);
104
+ for (const nested of copy?.querySelectorAll?.("ul, ol") ?? []) nested.remove?.();
105
+ for (const input of copy?.querySelectorAll?.('input[type="checkbox"]') ?? []) input.remove?.();
106
+ return String(copy?.textContent ?? "").trim();
107
+ }
108
+
109
+ function webHeadingValue(heading)
110
+ {
111
+ const single = String(heading?.id ?? "").startsWith("select");
112
+ const list = firstHeadingTaskList(heading);
113
+ if (!list) return single ? null : [];
114
+
115
+ const selected = [];
116
+ for (const item of list.children ?? []) {
117
+ if (!item.matches?.("li.task-list-item")) continue;
118
+ const checkbox = directTaskCheckbox(item);
119
+ if (!checkbox?.checked) continue;
120
+ const value = webTaskItemValue(item, checkbox);
121
+ if (single) return value;
122
+ selected.push(value);
123
+ }
124
+ return single ? null : selected;
125
+ }
126
+
127
+ function resizeWebTextarea(element)
128
+ {
129
+ if (!element || String(element.tagName ?? "").toLowerCase() !== "textarea") return;
130
+ try {
131
+ element.style.height = "auto";
132
+ element.style.height = `${element.scrollHeight}px`;
133
+ const lineHeight = Number.parseFloat(
134
+ element.ownerDocument?.defaultView?.getComputedStyle?.(element)?.lineHeight,
135
+ );
136
+ if (Number.isFinite(lineHeight) && lineHeight > 0)
137
+ element.rows = Math.max(1, Math.ceil(element.scrollHeight / lineHeight));
138
+ } catch {}
139
+ }
140
+
141
+ function installWebTextareaResize(target)
142
+ {
143
+ const documentObject = target?.document;
144
+ if (!documentObject || documentObject.__mdcuiTextareaResizeInstalled) return;
145
+ documentObject.__mdcuiTextareaResizeInstalled = true;
146
+ const resizeAll = () => {
147
+ for (const element of documentObject.querySelectorAll?.("textarea[data-mdcui-tag]") ?? [])
148
+ resizeWebTextarea(element);
149
+ };
150
+ documentObject.addEventListener?.("input", event => {
151
+ if (event.target?.matches?.("textarea[data-mdcui-tag]"))
152
+ resizeWebTextarea(event.target);
153
+ });
154
+ target.addEventListener?.("resize", resizeAll);
155
+ if (documentObject.readyState === "loading")
156
+ documentObject.addEventListener?.("DOMContentLoaded", resizeAll, { once: true });
157
+ else
158
+ queueMicrotask(resizeAll);
159
+ }
160
+
161
+ export function createWebDollar(documentObject = globalThis.document)
162
+ {
163
+ return function $(selectorText) {
164
+ const selector = parseDollarIdentity(selectorText, { selector: true });
165
+ const selection = {
166
+ html() {
167
+ try {
168
+ const element = findWebDollarElement(documentObject, selectorText, selector);
169
+ return element ? String(element.innerHTML ?? "") : "";
170
+ } catch {
171
+ return "";
172
+ }
173
+ },
174
+ val(...args) {
175
+ try {
176
+ const element = findWebDollarElement(documentObject, selectorText, selector);
177
+ if (!element) return args.length > 0 ? selection : "";
178
+ if (isWebHeading(element)) {
179
+ if (args.length > 0) return selection;
180
+ return webHeadingValue(element);
181
+ }
182
+ if (!selector) return args.length > 0 ? selection : "";
183
+ if (args.length > 0) {
184
+ const value = String(args[0] ?? "");
185
+ if ("value" in element) {
186
+ element.value = value;
187
+ resizeWebTextarea(element);
188
+ }
189
+ else element.textContent = value;
190
+ return selection;
191
+ }
192
+ return webDollarValue(element);
193
+ } catch {
194
+ return args.length > 0 ? selection : "";
195
+ }
196
+ },
197
+ };
198
+ return selection;
199
+ };
200
+ }
201
+
202
+ export function installWebDollar(target = globalThis)
203
+ {
204
+ if (!target?.document) return target?.$;
205
+ const $ = createWebDollar(target.document);
206
+ target.$ = $;
207
+ installWebTextareaResize(target);
208
+ return $;
209
+ }
210
+
211
+ if (typeof globalThis.document !== "undefined")
212
+ installWebDollar(globalThis);
213
+
12
214
 
13
215
  export async function evalBack(backmod, qjson)
14
216
  {
@@ -99,8 +301,11 @@ try{
99
301
 
100
302
  text = text.replace(/^javascript:/, "");
101
303
 
102
- const names = Object.keys(mod);
103
- const values = Object.values(mod).map(safeFrontValue);
304
+ const entries = Object.entries(mod).filter(([name]) => name !== "$");
305
+ if (typeof globalThis.$ === "function")
306
+ entries.push(["$", globalThis.$]);
307
+ const names = entries.map(([name]) => name);
308
+ const values = entries.map(([, value]) => safeFrontValue(value));
104
309
 
105
310
  try {
106
311
  return await new AsyncFunction(...names, `return await (${text})`)(...values);
@@ -0,0 +1,16 @@
1
+ export function toggleTaskCheckboxBeforeColumn(line, column) {
2
+ const text = String(line ?? "");
3
+ const end = Math.max(0, Math.min(text.length, Math.trunc(Number(column) || 0) + 1));
4
+ const prefix = text.slice(0, end);
5
+ const uncheckedAt = prefix.lastIndexOf("☐");
6
+ const checkedAt = prefix.lastIndexOf("☒");
7
+ const checkboxAt = Math.max(uncheckedAt, checkedAt);
8
+
9
+ if (checkboxAt < 0) return { line: text, toggled: false };
10
+
11
+ const replacement = text[checkboxAt] === "☐" ? "☒" : "☐";
12
+ return {
13
+ line: text.slice(0, checkboxAt) + replacement + text.slice(checkboxAt + 1),
14
+ toggled: true,
15
+ };
16
+ }