eddie-jekyll 0.4.0 → 0.4.2
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.
- checksums.yaml +4 -4
- data/assets/assets.list +46 -0
- data/assets/eddie-agent-worker.js +425 -291
- data/assets/eddie-boot.js +234 -0
- data/assets/eddie-dense-esm.js +624 -0
- data/assets/eddie-dense.js +632 -0
- data/assets/eddie-dense.wasm +0 -0
- data/assets/eddie-lite-esm.js +578 -0
- data/assets/{eddie-wasm.js → eddie-lite.js} +105 -61
- data/assets/eddie-lite.wasm +0 -0
- data/assets/eddie-sw-agent.js +846 -0
- data/assets/eddie-sw-dense.js +1873 -0
- data/assets/eddie-sw-gpu.js +1873 -0
- data/assets/eddie-sw-lite.js +1872 -0
- data/assets/eddie-transformers-sw.js +30926 -0
- data/assets/eddie-widget.js +1908 -449
- data/assets/eddie-worker.js +1205 -652
- data/scripts/install.sh +17 -9
- metadata +16 -5
- data/assets/eddie.wasm +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
// Generated by widget/build.sh from widget/src/eddie-boot.js and widget/src/lib/*.js; edit those instead.
|
|
3
|
+
(function () {
|
|
4
|
+
"use strict";
|
|
5
|
+
const EDDIE_ASSET_VERSION = "c0fdc503e01a";
|
|
6
|
+
const EddieLib = {};
|
|
7
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
8
|
+
|
|
9
|
+
// Boot loader decisions (widget/src/eddie-boot.js). Pure and tiny: the boot
|
|
10
|
+
// script is on every page view, so it carries only what it needs to draw
|
|
11
|
+
// the trigger button and decide when to fetch the full widget.
|
|
12
|
+
|
|
13
|
+
(function (factory) {
|
|
14
|
+
const api = factory();
|
|
15
|
+
if (typeof module === "object" && module && module.exports) {
|
|
16
|
+
module.exports = api;
|
|
17
|
+
} else if (typeof EddieLib === "object" && EddieLib) {
|
|
18
|
+
Object.assign(EddieLib, api);
|
|
19
|
+
} else {
|
|
20
|
+
globalThis.EddieLib = Object.assign(globalThis.EddieLib || {}, api);
|
|
21
|
+
}
|
|
22
|
+
})(function () {
|
|
23
|
+
"use strict";
|
|
24
|
+
|
|
25
|
+
const POSITIONS = ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
26
|
+
const SEARCH_CONSENT_KEY = "eddie.search.consent";
|
|
27
|
+
// Set on the first modal open on this browser: the boot loader treats the
|
|
28
|
+
// visitor as returning from then on (see decideBoot / decideWarm).
|
|
29
|
+
const SEARCH_USED_KEY = "eddie.search.used";
|
|
30
|
+
|
|
31
|
+
/** Trigger placement and theme from the `data-*` attributes (same rules as config.js). */
|
|
32
|
+
function bootLayout(get) {
|
|
33
|
+
const pos = String(get("data-position") || "").trim().toLowerCase();
|
|
34
|
+
const theme = String(get("data-theme") || "").trim().toLowerCase();
|
|
35
|
+
const num = (raw) => {
|
|
36
|
+
const v = Number(raw);
|
|
37
|
+
return raw != null && raw !== "" && Number.isFinite(v) ? Math.trunc(v) : 0;
|
|
38
|
+
};
|
|
39
|
+
const warm = String(get("data-warm") || "").trim().toLowerCase();
|
|
40
|
+
return {
|
|
41
|
+
position: POSITIONS.includes(pos) ? pos : "bottom-right",
|
|
42
|
+
theme: ["auto", "light", "dark"].includes(theme) ? theme : "auto",
|
|
43
|
+
offsetX: num(get("data-offset-x")),
|
|
44
|
+
offsetY: num(get("data-offset-y")),
|
|
45
|
+
warm: ["auto", "off", "always"].includes(warm) ? warm : "auto",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* When the boot loader fetches the full widget.
|
|
51
|
+
* warm "auto" | "off" | "always" (data-warm)
|
|
52
|
+
* saveData navigator.connection.saveData
|
|
53
|
+
* reducedData prefers-reduced-data: reduce
|
|
54
|
+
* used the visitor opened the search before on this browser
|
|
55
|
+
* consented a dense lane was accepted before on this browser
|
|
56
|
+
* Returns { action: "idle" | "interaction", reason }: "idle" loads the
|
|
57
|
+
* widget after the page has loaded and the browser is idle (the widget then
|
|
58
|
+
* runs its own warm-up), "interaction" waits for the trigger, the shortcut
|
|
59
|
+
* or a programmatic open.
|
|
60
|
+
*/
|
|
61
|
+
function decideBoot(opts) {
|
|
62
|
+
const o = opts || {};
|
|
63
|
+
if (o.warm === "off") return { action: "interaction", reason: "warm is off" };
|
|
64
|
+
if (o.saveData) return { action: "interaction", reason: "data saver" };
|
|
65
|
+
if (o.reducedData) return { action: "interaction", reason: "prefers-reduced-data" };
|
|
66
|
+
if (o.warm === "always") return { action: "idle", reason: "warm is always" };
|
|
67
|
+
if (o.consented) return { action: "idle", reason: "model consented before" };
|
|
68
|
+
if (o.used) return { action: "idle", reason: "search used before" };
|
|
69
|
+
return { action: "interaction", reason: "first visit" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** `v` query parameter of a URL string, or null. */
|
|
73
|
+
function bootVersionOf(href) {
|
|
74
|
+
const m = /[?&]v=([^&#]+)/.exec(String(href || ""));
|
|
75
|
+
return m ? decodeURIComponent(m[1]) : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Runtime-asset `?v=`, stamped by widget/build.sh; see lib/urls.js. */
|
|
79
|
+
const ASSET_VERSION =
|
|
80
|
+
typeof EDDIE_ASSET_VERSION === "string" && EDDIE_ASSET_VERSION ? EDDIE_ASSET_VERSION : null;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* URL of the full widget next to the boot script, versioned by the asset
|
|
84
|
+
* hash so a content rebuild does not re-download it. An explicit `?v=` on
|
|
85
|
+
* the boot script's own URL wins (a site pinning a build); the index's
|
|
86
|
+
* `?v=` is the last resort, for a bundle without the stamp.
|
|
87
|
+
*/
|
|
88
|
+
function widgetScriptUrl(scriptHref, indexUrl) {
|
|
89
|
+
const base = scriptHref.split(/[?#]/)[0];
|
|
90
|
+
const dir = base.substring(0, base.lastIndexOf("/") + 1);
|
|
91
|
+
const v = bootVersionOf(scriptHref) || ASSET_VERSION || bootVersionOf(indexUrl);
|
|
92
|
+
return dir + "eddie-widget.js" + (v ? "?v=" + encodeURIComponent(v) : "");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { SEARCH_CONSENT_KEY, SEARCH_USED_KEY, bootLayout, decideBoot, bootVersionOf, widgetScriptUrl };
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
99
|
+
|
|
100
|
+
// Eddie boot loader: the script a page carries by default. It draws the
|
|
101
|
+
// trigger button (same data-* attributes, placement and theme as the full
|
|
102
|
+
// widget) and installs the Ctrl/Cmd+K shortcut; the full widget
|
|
103
|
+
// (eddie-widget.js, next to this file, same ?v=) is fetched on the first
|
|
104
|
+
// interaction with the trigger, on the shortcut, on `window.eddie.open()`,
|
|
105
|
+
// or after load when lib/boot.js decides the visitor is returning (the full
|
|
106
|
+
// widget then runs its own warm-up). A first-time visitor pays for this
|
|
107
|
+
// file only.
|
|
108
|
+
//
|
|
109
|
+
// widget/build.sh concatenates widget/src/lib/boot.js ahead of this file
|
|
110
|
+
// inside one IIFE (`EddieLib`).
|
|
111
|
+
|
|
112
|
+
"use strict";
|
|
113
|
+
|
|
114
|
+
(function () {
|
|
115
|
+
const scriptEl = document.currentScript;
|
|
116
|
+
if (!scriptEl || document.getElementById("eddie-host") || document.getElementById("eddie-boot")) return;
|
|
117
|
+
const lib = EddieLib;
|
|
118
|
+
const get = (name) => scriptEl.getAttribute(name);
|
|
119
|
+
const layout = lib.bootLayout(get);
|
|
120
|
+
const scriptHref = new URL(scriptEl.src, location.href).href;
|
|
121
|
+
const widgetUrl = lib.widgetScriptUrl(scriptHref, get("data-index-url") || "");
|
|
122
|
+
|
|
123
|
+
const host = document.createElement("div");
|
|
124
|
+
host.id = "eddie-boot";
|
|
125
|
+
host.dataset.theme = layout.theme;
|
|
126
|
+
const shadow = host.attachShadow({ mode: "closed" });
|
|
127
|
+
const style = document.createElement("style");
|
|
128
|
+
style.textContent =
|
|
129
|
+
":host{all:initial;position:fixed;z-index:999999;--b:#fff;--t:#1a1a1a;--l:#e0e0e0;--a:#2563eb}" +
|
|
130
|
+
":host([data-theme=dark]){--b:#1a1a1a;--t:#e8e8e8;--l:#333;--a:#60a5fa}" +
|
|
131
|
+
"@media(prefers-color-scheme:dark){:host([data-theme=auto]){--b:#1a1a1a;--t:#e8e8e8;--l:#333;--a:#60a5fa}}" +
|
|
132
|
+
"button{position:fixed;width:48px;height:48px;border-radius:50%;border:1px solid var(--l);background:var(--b);color:var(--t);cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;box-shadow:0 2px 12px rgba(0,0,0,.1);transition:transform .15s ease,box-shadow .15s ease}" +
|
|
133
|
+
"button:hover{transform:scale(1.06);box-shadow:0 4px 16px rgba(0,0,0,.15)}button:active{transform:scale(.96)}" +
|
|
134
|
+
"button:focus-visible{outline:2px solid var(--a);outline-offset:2px}button[aria-busy=true]{opacity:.6}" +
|
|
135
|
+
"svg{width:20px;height:20px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}" +
|
|
136
|
+
"@media(prefers-reduced-motion:reduce){button{transition:none}}";
|
|
137
|
+
shadow.appendChild(style);
|
|
138
|
+
|
|
139
|
+
const trigger = document.createElement("button");
|
|
140
|
+
trigger.type = "button";
|
|
141
|
+
trigger.setAttribute("aria-label", "Search");
|
|
142
|
+
const v = `${24 + layout.offsetY}px`;
|
|
143
|
+
const h = `${24 + layout.offsetX}px`;
|
|
144
|
+
trigger.style[layout.position.startsWith("top") ? "top" : "bottom"] = v;
|
|
145
|
+
trigger.style[layout.position.endsWith("left") ? "left" : "right"] = h;
|
|
146
|
+
trigger.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>';
|
|
147
|
+
shadow.appendChild(trigger);
|
|
148
|
+
|
|
149
|
+
let loading = null; // the injected <script>, once requested
|
|
150
|
+
const handoff = {
|
|
151
|
+
open: false, // the visitor asked to open before the widget arrived
|
|
152
|
+
load,
|
|
153
|
+
dispose() {
|
|
154
|
+
if (host.parentNode) host.parentNode.removeChild(host);
|
|
155
|
+
document.removeEventListener("keydown", onKey);
|
|
156
|
+
if (window.__eddieBoot === handoff) delete window.__eddieBoot;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
window.__eddieBoot = handoff;
|
|
160
|
+
|
|
161
|
+
/** Fetch the full widget once; it reads this script's data-* attributes from its own tag. */
|
|
162
|
+
function load(reason) {
|
|
163
|
+
if (loading) return;
|
|
164
|
+
const s = document.createElement("script");
|
|
165
|
+
for (const attr of Array.from(scriptEl.attributes)) {
|
|
166
|
+
if (attr.name.startsWith("data-")) s.setAttribute(attr.name, attr.value);
|
|
167
|
+
}
|
|
168
|
+
s.setAttribute("data-boot", reason || "");
|
|
169
|
+
s.src = widgetUrl;
|
|
170
|
+
s.async = true;
|
|
171
|
+
s.onerror = () => {
|
|
172
|
+
loading = null; // the next interaction retries
|
|
173
|
+
trigger.removeAttribute("aria-busy");
|
|
174
|
+
};
|
|
175
|
+
loading = s;
|
|
176
|
+
document.head.appendChild(s);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function open() {
|
|
180
|
+
handoff.open = true;
|
|
181
|
+
trigger.setAttribute("aria-busy", "true");
|
|
182
|
+
load("open");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isEditable(node) {
|
|
186
|
+
if (!node || node.nodeType !== 1) return false;
|
|
187
|
+
if (node.isContentEditable) return true;
|
|
188
|
+
const tag = node.tagName;
|
|
189
|
+
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function onKey(e) {
|
|
193
|
+
if (!(e.ctrlKey || e.metaKey) || e.altKey || typeof e.key !== "string" || e.key.toLowerCase() !== "k") return;
|
|
194
|
+
if (isEditable(e.composedPath ? e.composedPath()[0] : e.target)) return;
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
open();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
trigger.addEventListener("pointerover", () => load("hover"), { once: true });
|
|
200
|
+
trigger.addEventListener("focusin", () => load("focus"), { once: true });
|
|
201
|
+
trigger.addEventListener("click", open);
|
|
202
|
+
document.addEventListener("keydown", onKey);
|
|
203
|
+
// `window.eddie.open()` works before and after the full widget arrives.
|
|
204
|
+
window.eddie = Object.assign(window.eddie || {}, { open });
|
|
205
|
+
document.body.appendChild(host);
|
|
206
|
+
|
|
207
|
+
let saveData = false;
|
|
208
|
+
let reducedData = false;
|
|
209
|
+
try {
|
|
210
|
+
saveData = !!(navigator.connection && navigator.connection.saveData);
|
|
211
|
+
reducedData = !!(window.matchMedia && window.matchMedia("(prefers-reduced-data: reduce)").matches);
|
|
212
|
+
} catch (_) {
|
|
213
|
+
// no connection info: treat as a normal connection
|
|
214
|
+
}
|
|
215
|
+
let used = false;
|
|
216
|
+
let consented = false;
|
|
217
|
+
try {
|
|
218
|
+
used = !!localStorage.getItem(lib.SEARCH_USED_KEY);
|
|
219
|
+
consented = !!localStorage.getItem(lib.SEARCH_CONSENT_KEY);
|
|
220
|
+
} catch (_) {
|
|
221
|
+
// storage unavailable: first-visit rules
|
|
222
|
+
}
|
|
223
|
+
const decision = lib.decideBoot({ warm: layout.warm, saveData, reducedData, used, consented });
|
|
224
|
+
host.dataset.boot = decision.action;
|
|
225
|
+
if (decision.action === "idle") {
|
|
226
|
+
const go = () => {
|
|
227
|
+
if (typeof requestIdleCallback === "function") requestIdleCallback(() => load("idle"), { timeout: 3000 });
|
|
228
|
+
else setTimeout(() => load("idle"), 500);
|
|
229
|
+
};
|
|
230
|
+
if (document.readyState === "complete") go();
|
|
231
|
+
else window.addEventListener("load", go, { once: true });
|
|
232
|
+
}
|
|
233
|
+
})();
|
|
234
|
+
})();
|