efficient-shell 0.0.0 → 4.126.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.
- package/LICENSE +21 -0
- package/README.md +752 -4
- package/dist/Browser-DBRSIQ4J.js +478 -0
- package/dist/Browser-DBRSIQ4J.js.map +1 -0
- package/dist/Calculator-HCKFTSJR.js +188 -0
- package/dist/Calculator-HCKFTSJR.js.map +1 -0
- package/dist/ConfirmDialog-LJXMB45A.js +3 -0
- package/dist/ConfirmDialog-LJXMB45A.js.map +1 -0
- package/dist/CurrencyConverter-BKKORN6K.js +232 -0
- package/dist/CurrencyConverter-BKKORN6K.js.map +1 -0
- package/dist/Documents-5MD5SKPB.js +437 -0
- package/dist/Documents-5MD5SKPB.js.map +1 -0
- package/dist/Files-KY35DPWB.js +801 -0
- package/dist/Files-KY35DPWB.js.map +1 -0
- package/dist/Notepad-2BO5VEUG.js +406 -0
- package/dist/Notepad-2BO5VEUG.js.map +1 -0
- package/dist/PdfViewer-LSP3YNFC.js +4 -0
- package/dist/PdfViewer-LSP3YNFC.js.map +1 -0
- package/dist/PomodoroTimer-SVJ62RXM.js +629 -0
- package/dist/PomodoroTimer-SVJ62RXM.js.map +1 -0
- package/dist/Preview-N4UYYXG6.js +2955 -0
- package/dist/Preview-N4UYYXG6.js.map +1 -0
- package/dist/Sidebar-J76PV4QF.js +290 -0
- package/dist/Sidebar-J76PV4QF.js.map +1 -0
- package/dist/Spreadsheet-5EWZGMXN.js +515 -0
- package/dist/Spreadsheet-5EWZGMXN.js.map +1 -0
- package/dist/Stock-CDUS4VCZ.js +154 -0
- package/dist/Stock-CDUS4VCZ.js.map +1 -0
- package/dist/Weather-EEDBWBPQ.js +427 -0
- package/dist/Weather-EEDBWBPQ.js.map +1 -0
- package/dist/WorldClock-FFFULVTJ.js +198 -0
- package/dist/WorldClock-FFFULVTJ.js.map +1 -0
- package/dist/apps/index.d.ts +267 -0
- package/dist/apps/index.js +147 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/brand.css +2 -0
- package/dist/chunk-2VM2FRZJ.js +215 -0
- package/dist/chunk-2VM2FRZJ.js.map +1 -0
- package/dist/chunk-32W6VY2J.js +549 -0
- package/dist/chunk-32W6VY2J.js.map +1 -0
- package/dist/chunk-3CUSBZWG.js +59 -0
- package/dist/chunk-3CUSBZWG.js.map +1 -0
- package/dist/chunk-3L463T5H.js +26 -0
- package/dist/chunk-3L463T5H.js.map +1 -0
- package/dist/chunk-4TVV2BIU.js +13334 -0
- package/dist/chunk-4TVV2BIU.js.map +1 -0
- package/dist/chunk-6XQHH7D2.js +105 -0
- package/dist/chunk-6XQHH7D2.js.map +1 -0
- package/dist/chunk-7CCHEEYC.js +94 -0
- package/dist/chunk-7CCHEEYC.js.map +1 -0
- package/dist/chunk-7KIOEKHS.js +617 -0
- package/dist/chunk-7KIOEKHS.js.map +1 -0
- package/dist/chunk-AXWC3332.js +1103 -0
- package/dist/chunk-AXWC3332.js.map +1 -0
- package/dist/chunk-BFBWLCO6.js +16 -0
- package/dist/chunk-BFBWLCO6.js.map +1 -0
- package/dist/chunk-C3NELUTX.js +79 -0
- package/dist/chunk-C3NELUTX.js.map +1 -0
- package/dist/chunk-CT3HVZRZ.js +26 -0
- package/dist/chunk-CT3HVZRZ.js.map +1 -0
- package/dist/chunk-D7PYW2QS.js +265 -0
- package/dist/chunk-D7PYW2QS.js.map +1 -0
- package/dist/chunk-DMCFOWOJ.js +153 -0
- package/dist/chunk-DMCFOWOJ.js.map +1 -0
- package/dist/chunk-FQB24B42.js +416 -0
- package/dist/chunk-FQB24B42.js.map +1 -0
- package/dist/chunk-GA35NEND.js +56 -0
- package/dist/chunk-GA35NEND.js.map +1 -0
- package/dist/chunk-HNH7FOYB.js +69 -0
- package/dist/chunk-HNH7FOYB.js.map +1 -0
- package/dist/chunk-HYZ5QMVO.js +462 -0
- package/dist/chunk-HYZ5QMVO.js.map +1 -0
- package/dist/chunk-I464GYJJ.js +17 -0
- package/dist/chunk-I464GYJJ.js.map +1 -0
- package/dist/chunk-JFQGPY7W.js +819 -0
- package/dist/chunk-JFQGPY7W.js.map +1 -0
- package/dist/chunk-MGGY74RY.js +14 -0
- package/dist/chunk-MGGY74RY.js.map +1 -0
- package/dist/chunk-N3CG5HG5.js +144 -0
- package/dist/chunk-N3CG5HG5.js.map +1 -0
- package/dist/chunk-W6XTG23K.js +3708 -0
- package/dist/chunk-W6XTG23K.js.map +1 -0
- package/dist/chunk-YG45DAUT.js +96 -0
- package/dist/chunk-YG45DAUT.js.map +1 -0
- package/dist/chunk-YMGXZABY.js +7 -0
- package/dist/chunk-YMGXZABY.js.map +1 -0
- package/dist/chunk-YNGPTOL6.js +46 -0
- package/dist/chunk-YNGPTOL6.js.map +1 -0
- package/dist/chunk-YNHLQ24V.js +400 -0
- package/dist/chunk-YNHLQ24V.js.map +1 -0
- package/dist/chunk-YNKK7NE3.js +171 -0
- package/dist/chunk-YNKK7NE3.js.map +1 -0
- package/dist/chunk-YPO6RDMX.js +20 -0
- package/dist/chunk-YPO6RDMX.js.map +1 -0
- package/dist/file-intake/index.d.ts +87 -0
- package/dist/file-intake/index.js +3 -0
- package/dist/file-intake/index.js.map +1 -0
- package/dist/index-dQia6NpB.d.ts +5904 -0
- package/dist/index.d.ts +2495 -0
- package/dist/index.js +7460 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/index.d.ts +118 -0
- package/dist/markdown/index.js +184 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markup/index.d.ts +235 -0
- package/dist/markup/index.js +135 -0
- package/dist/markup/index.js.map +1 -0
- package/dist/page-tabs/index.d.ts +154 -0
- package/dist/page-tabs/index.js +706 -0
- package/dist/page-tabs/index.js.map +1 -0
- package/dist/shell.css +105 -0
- package/dist/sounds-NT4DEZGD.js +3 -0
- package/dist/sounds-NT4DEZGD.js.map +1 -0
- package/dist/styles.css +22 -0
- package/dist/themes.css +249 -0
- package/dist/types-BOinJXUf.d.ts +118 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui.css +1518 -0
- package/package.json +172 -3
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { peekBrowserStartUrl, claimBrowserStartUrl } from './chunk-BFBWLCO6.js';
|
|
2
|
+
import { AboutApp } from './chunk-C3NELUTX.js';
|
|
3
|
+
import { WindowTitle } from './chunk-W6XTG23K.js';
|
|
4
|
+
import './chunk-GA35NEND.js';
|
|
5
|
+
import './chunk-YMGXZABY.js';
|
|
6
|
+
import './chunk-YNHLQ24V.js';
|
|
7
|
+
import './chunk-7KIOEKHS.js';
|
|
8
|
+
import { confirm } from './chunk-HYZ5QMVO.js';
|
|
9
|
+
import './chunk-3CUSBZWG.js';
|
|
10
|
+
import { useRef, useEffect, useState } from 'react';
|
|
11
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
12
|
+
|
|
13
|
+
var BOOKMARKS_KEY = "react-os-shell:browser-bookmarks";
|
|
14
|
+
var HOMEPAGE_KEY = "react-os-shell:browser-homepage";
|
|
15
|
+
var DEFAULT_HOMEPAGE = "https://en.wikipedia.org/wiki/Main_Page";
|
|
16
|
+
var SEARCH_URL = "https://duckduckgo.com/?q=";
|
|
17
|
+
var DEFAULT_BOOKMARKS = [
|
|
18
|
+
{ label: "Wikipedia", url: "https://en.wikipedia.org/wiki/Main_Page" },
|
|
19
|
+
// DevDocs rather than MDN: MDN sends `x-frame-options: DENY`, so shipping it
|
|
20
|
+
// as a default bookmark shipped a guaranteed-blank pane (BG#00374). DevDocs
|
|
21
|
+
// carries the same API reference content and permits embedding.
|
|
22
|
+
{ label: "DevDocs", url: "https://devdocs.io" },
|
|
23
|
+
{ label: "Example", url: "https://example.com" }
|
|
24
|
+
];
|
|
25
|
+
var KNOWN_TLDS = new Set([
|
|
26
|
+
// ISO 3166-1 alpha-2 ccTLDs. Mind the lookalikes: `md`, `py`, `rs` and `sh`
|
|
27
|
+
// ARE countries (`foo.sh` is a real host), while `js` and `ts` are not.
|
|
28
|
+
"ac ad ae af ag ai al am ao aq ar as at au aw ax az ba bb bd be bf bg bh bi",
|
|
29
|
+
"bj bm bn bo br bs bt bw by bz ca cc cd cf cg ch ci ck cl cm cn co cr cu cv",
|
|
30
|
+
"cw cx cy cz de dj dk dm do dz ec ee eg er es et eu fi fj fk fm fo fr ga gd",
|
|
31
|
+
"ge gf gg gh gi gl gm gn gp gq gr gs gt gu gw gy hk hm hn hr ht hu id ie il",
|
|
32
|
+
"im in io iq ir is it je jm jo jp ke kg kh ki km kn kp kr kw ky kz la lb lc",
|
|
33
|
+
"li lk lr ls lt lu lv ly ma mc md me mg mh mk ml mm mn mo mp mq mr ms mt mu",
|
|
34
|
+
"mv mw mx my mz na nc ne nf ng ni nl no np nr nu nz om pa pe pf pg ph pk pl",
|
|
35
|
+
"pm pn pr ps pt pw py qa re ro rs ru rw sa sb sc sd se sg sh si sk sl sm sn",
|
|
36
|
+
"so sr ss st su sv sx sy sz tc td tf tg th tj tk tl tm tn to tr tt tv tw tz",
|
|
37
|
+
"ua ug uk us uy uz va vc ve vg vi vn vu wf ws ye yt za zm zw",
|
|
38
|
+
// Common gTLDs, plus the ones this business actually uses (`design`).
|
|
39
|
+
"com net org edu gov mil int info biz name pro app dev xyz online site tech",
|
|
40
|
+
"store shop cloud design digital agency studio media news blog live life",
|
|
41
|
+
"world today email systems solutions services software page link space host",
|
|
42
|
+
"press wiki team group works careers finance capital energy academy"
|
|
43
|
+
].join(" ").split(" "));
|
|
44
|
+
function isProbableHost(s) {
|
|
45
|
+
const m = /^([\w-]+\.)+([a-z]{2,})(\/.*)?$/i.exec(s);
|
|
46
|
+
return m !== null && KNOWN_TLDS.has(m[2].toLowerCase());
|
|
47
|
+
}
|
|
48
|
+
function normalizeUrl(input) {
|
|
49
|
+
let s = input.trim();
|
|
50
|
+
if (!s) return "";
|
|
51
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return s;
|
|
52
|
+
if (isProbableHost(s)) return "https://" + s;
|
|
53
|
+
return SEARCH_URL + encodeURIComponent(s);
|
|
54
|
+
}
|
|
55
|
+
var BLOCKED_HOSTS = [
|
|
56
|
+
// The two hosts this app itself routes to or ships as a default bookmark.
|
|
57
|
+
// Their absence here was BG#00374: an address-bar search went to a blank
|
|
58
|
+
// pane rather than the panel below, reading as "search does nothing".
|
|
59
|
+
// Verified live 2026-07-16 — duckduckgo.com: `x-frame-options: SAMEORIGIN` +
|
|
60
|
+
// `frame-ancestors 'self' https://html.duckduckgo.com` (the html/ and lite/
|
|
61
|
+
// mirrors are no escape hatch: `frame-ancestors 'self'`);
|
|
62
|
+
// developer.mozilla.org: `x-frame-options: DENY` (kept even though the
|
|
63
|
+
// default bookmark moved to DevDocs — existing users have MDN persisted in
|
|
64
|
+
// localStorage, and it stays a thing people type).
|
|
65
|
+
"duckduckgo.com",
|
|
66
|
+
"developer.mozilla.org",
|
|
67
|
+
"google.com",
|
|
68
|
+
"gmail.com",
|
|
69
|
+
"youtube.com",
|
|
70
|
+
"facebook.com",
|
|
71
|
+
"instagram.com",
|
|
72
|
+
"whatsapp.com",
|
|
73
|
+
"twitter.com",
|
|
74
|
+
"x.com",
|
|
75
|
+
"github.com",
|
|
76
|
+
"gitlab.com",
|
|
77
|
+
"linkedin.com",
|
|
78
|
+
"reddit.com",
|
|
79
|
+
"pinterest.com",
|
|
80
|
+
"amazon.com",
|
|
81
|
+
"amazon.ca",
|
|
82
|
+
"amazon.co.uk",
|
|
83
|
+
"apple.com",
|
|
84
|
+
"icloud.com",
|
|
85
|
+
"microsoft.com",
|
|
86
|
+
"outlook.com",
|
|
87
|
+
"live.com",
|
|
88
|
+
"office.com",
|
|
89
|
+
"netflix.com",
|
|
90
|
+
"spotify.com",
|
|
91
|
+
"paypal.com",
|
|
92
|
+
"stripe.com",
|
|
93
|
+
"chat.openai.com",
|
|
94
|
+
"chatgpt.com",
|
|
95
|
+
"claude.ai"
|
|
96
|
+
];
|
|
97
|
+
function hostIsBlocked(href) {
|
|
98
|
+
try {
|
|
99
|
+
const host = new URL(href).hostname.toLowerCase();
|
|
100
|
+
return BLOCKED_HOSTS.some((b) => host === b || host.endsWith("." + b));
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function loadBookmarks() {
|
|
106
|
+
if (typeof window === "undefined") return DEFAULT_BOOKMARKS;
|
|
107
|
+
try {
|
|
108
|
+
const raw = localStorage.getItem(BOOKMARKS_KEY);
|
|
109
|
+
if (!raw) return DEFAULT_BOOKMARKS;
|
|
110
|
+
const parsed = JSON.parse(raw);
|
|
111
|
+
if (Array.isArray(parsed) && parsed.every((b) => typeof b?.url === "string")) return parsed;
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
return DEFAULT_BOOKMARKS;
|
|
115
|
+
}
|
|
116
|
+
function loadHomepage() {
|
|
117
|
+
if (typeof window === "undefined") return DEFAULT_HOMEPAGE;
|
|
118
|
+
return localStorage.getItem(HOMEPAGE_KEY) || DEFAULT_HOMEPAGE;
|
|
119
|
+
}
|
|
120
|
+
function Browser() {
|
|
121
|
+
const consumedRef = useRef(void 0);
|
|
122
|
+
if (consumedRef.current === void 0) {
|
|
123
|
+
consumedRef.current = peekBrowserStartUrl();
|
|
124
|
+
}
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (consumedRef.current != null) {
|
|
127
|
+
claimBrowserStartUrl(consumedRef.current);
|
|
128
|
+
}
|
|
129
|
+
}, []);
|
|
130
|
+
const startUrl = consumedRef.current ? normalizeUrl(consumedRef.current.url) : "";
|
|
131
|
+
const [homepage, setHomepage] = useState(loadHomepage);
|
|
132
|
+
const [url, setUrl] = useState(startUrl || homepage);
|
|
133
|
+
const [inputUrl, setInputUrl] = useState(url);
|
|
134
|
+
const [history, setHistory] = useState([url]);
|
|
135
|
+
const [historyIdx, setHistoryIdx] = useState(0);
|
|
136
|
+
const [iframeKey, setIframeKey] = useState(0);
|
|
137
|
+
const [bookmarks, setBookmarks] = useState(loadBookmarks);
|
|
138
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
139
|
+
const [bookmarkDraft, setBookmarkDraft] = useState(null);
|
|
140
|
+
const bookmarkInputRef = useRef(null);
|
|
141
|
+
const iframeRef = useRef(null);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
try {
|
|
144
|
+
localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(bookmarks));
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
}, [bookmarks]);
|
|
148
|
+
const navigate = (next) => {
|
|
149
|
+
const n = normalizeUrl(next);
|
|
150
|
+
if (!n) return;
|
|
151
|
+
setUrl(n);
|
|
152
|
+
setInputUrl(n);
|
|
153
|
+
setHistory((h) => {
|
|
154
|
+
const trimmed = h.slice(0, historyIdx + 1);
|
|
155
|
+
trimmed.push(n);
|
|
156
|
+
return trimmed;
|
|
157
|
+
});
|
|
158
|
+
setHistoryIdx((i) => i + 1);
|
|
159
|
+
setShowHelp(false);
|
|
160
|
+
};
|
|
161
|
+
const back = () => {
|
|
162
|
+
if (historyIdx > 0) {
|
|
163
|
+
const next = historyIdx - 1;
|
|
164
|
+
setHistoryIdx(next);
|
|
165
|
+
setUrl(history[next]);
|
|
166
|
+
setInputUrl(history[next]);
|
|
167
|
+
setShowHelp(false);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const forward = () => {
|
|
171
|
+
if (historyIdx < history.length - 1) {
|
|
172
|
+
const next = historyIdx + 1;
|
|
173
|
+
setHistoryIdx(next);
|
|
174
|
+
setUrl(history[next]);
|
|
175
|
+
setInputUrl(history[next]);
|
|
176
|
+
setShowHelp(false);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const refresh = () => setIframeKey((k) => k + 1);
|
|
180
|
+
const goHome = () => navigate(homepage);
|
|
181
|
+
const openExternal = () => {
|
|
182
|
+
if (typeof window !== "undefined") window.open(url, "_blank", "noopener,noreferrer");
|
|
183
|
+
};
|
|
184
|
+
const openBookmarkPopover = () => {
|
|
185
|
+
setBookmarkDraft(titleFromUrl(url));
|
|
186
|
+
};
|
|
187
|
+
const commitBookmark = () => {
|
|
188
|
+
const label = bookmarkDraft?.trim();
|
|
189
|
+
if (!label) {
|
|
190
|
+
setBookmarkDraft(null);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
setBookmarks((b) => [...b, { label, url }]);
|
|
194
|
+
setBookmarkDraft(null);
|
|
195
|
+
};
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (bookmarkDraft === null) return;
|
|
198
|
+
bookmarkInputRef.current?.focus();
|
|
199
|
+
bookmarkInputRef.current?.select();
|
|
200
|
+
const onDocClick = (e) => {
|
|
201
|
+
const target = e.target;
|
|
202
|
+
if (!target?.closest("[data-bookmark-popover]")) setBookmarkDraft(null);
|
|
203
|
+
};
|
|
204
|
+
const t = setTimeout(() => document.addEventListener("mousedown", onDocClick), 0);
|
|
205
|
+
return () => {
|
|
206
|
+
clearTimeout(t);
|
|
207
|
+
document.removeEventListener("mousedown", onDocClick);
|
|
208
|
+
};
|
|
209
|
+
}, [bookmarkDraft !== null]);
|
|
210
|
+
const removeBookmark = (i) => {
|
|
211
|
+
setBookmarks((b) => b.filter((_, idx) => idx !== i));
|
|
212
|
+
};
|
|
213
|
+
const setAsHomepage = () => {
|
|
214
|
+
setHomepage(url);
|
|
215
|
+
try {
|
|
216
|
+
localStorage.setItem(HOMEPAGE_KEY, url);
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const onSubmitUrl = (e) => {
|
|
221
|
+
e.preventDefault();
|
|
222
|
+
navigate(inputUrl);
|
|
223
|
+
};
|
|
224
|
+
const canBack = historyIdx > 0;
|
|
225
|
+
const canForward = historyIdx < history.length - 1;
|
|
226
|
+
const isBookmarked = bookmarks.some((b) => b.url === url);
|
|
227
|
+
const btn = "p-1.5 rounded hover:bg-gray-200 transition-colors text-gray-600 disabled:opacity-30";
|
|
228
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col h-full bg-white", children: [
|
|
229
|
+
/* @__PURE__ */ jsx(WindowTitle, { title: `Browser - ${titleFromUrl(url)}` }),
|
|
230
|
+
/* @__PURE__ */ jsx(AboutApp, { app: "browser" }),
|
|
231
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 px-2 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0", children: [
|
|
232
|
+
/* @__PURE__ */ jsx("button", { onClick: back, disabled: !canBack, className: btn, title: "Back", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15.75 19.5L8.25 12l7.5-7.5" }) }) }),
|
|
233
|
+
/* @__PURE__ */ jsx("button", { onClick: forward, disabled: !canForward, className: btn, title: "Forward", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) }) }),
|
|
234
|
+
/* @__PURE__ */ jsx("button", { onClick: refresh, className: btn, title: "Refresh", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992M3.05 9.348a9 9 0 0114.85-3.36L21.015 9.348m0 5.304a9 9 0 01-14.85 3.36l-3.115-3.36" }) }) }),
|
|
235
|
+
/* @__PURE__ */ jsx("button", { onClick: goHome, className: btn, title: "Home", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.25 12l8.954-8.955a1.5 1.5 0 012.122 0l8.954 8.955M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" }) }) }),
|
|
236
|
+
/* @__PURE__ */ jsx("form", { onSubmit: onSubmitUrl, className: "flex-1 flex items-center mx-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full bg-white border border-gray-300 rounded focus-within:border-blue-400 focus-within:ring-1 focus-within:ring-blue-200", children: [
|
|
237
|
+
/* @__PURE__ */ jsx("span", { className: "px-2 text-gray-400 text-xs", children: url.startsWith("https://") ? "\u{1F512}" : "\u26A0\uFE0F" }),
|
|
238
|
+
/* @__PURE__ */ jsx(
|
|
239
|
+
"input",
|
|
240
|
+
{
|
|
241
|
+
type: "text",
|
|
242
|
+
value: inputUrl,
|
|
243
|
+
onChange: (e) => setInputUrl(e.target.value),
|
|
244
|
+
onFocus: (e) => e.target.select(),
|
|
245
|
+
className: "flex-1 px-1 py-1 text-sm bg-transparent outline-none font-mono",
|
|
246
|
+
spellCheck: false,
|
|
247
|
+
placeholder: "Enter URL or search\u2026"
|
|
248
|
+
}
|
|
249
|
+
),
|
|
250
|
+
url !== inputUrl && /* @__PURE__ */ jsx(
|
|
251
|
+
"button",
|
|
252
|
+
{
|
|
253
|
+
type: "button",
|
|
254
|
+
onClick: () => setInputUrl(url),
|
|
255
|
+
className: "px-1.5 text-[10px] text-gray-400 hover:text-gray-700",
|
|
256
|
+
title: "Reset to current URL",
|
|
257
|
+
children: "\xD7"
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
] }) }),
|
|
261
|
+
/* @__PURE__ */ jsx(
|
|
262
|
+
"button",
|
|
263
|
+
{
|
|
264
|
+
"data-bookmark-popover": true,
|
|
265
|
+
onClick: () => isBookmarked ? removeBookmark(bookmarks.findIndex((b) => b.url === url)) : openBookmarkPopover(),
|
|
266
|
+
className: btn + " " + (isBookmarked ? "text-yellow-500" : ""),
|
|
267
|
+
title: isBookmarked ? "Remove bookmark" : "Add bookmark",
|
|
268
|
+
children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: isBookmarked ? "currentColor" : "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" }) })
|
|
269
|
+
}
|
|
270
|
+
),
|
|
271
|
+
/* @__PURE__ */ jsx("button", { onClick: openExternal, className: btn, title: "Open in new tab", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-7.5-9L21 3m0 0v6m0-6L9.75 14.25" }) }) }),
|
|
272
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setShowHelp((s) => !s), className: btn, title: "Embedding help", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" }) }) })
|
|
273
|
+
] }),
|
|
274
|
+
bookmarkDraft !== null && /* @__PURE__ */ jsxs(
|
|
275
|
+
"div",
|
|
276
|
+
{
|
|
277
|
+
"data-bookmark-popover": true,
|
|
278
|
+
className: "absolute right-3 top-12 z-30 w-72 bg-white border border-gray-200 rounded-md shadow-lg p-3",
|
|
279
|
+
children: [
|
|
280
|
+
/* @__PURE__ */ jsx("div", { className: "text-[11px] font-semibold uppercase tracking-wide text-gray-500 mb-1", children: "Bookmark this page" }),
|
|
281
|
+
/* @__PURE__ */ jsx("div", { className: "text-[11px] text-gray-400 truncate mb-2 font-mono", title: url, children: url }),
|
|
282
|
+
/* @__PURE__ */ jsx(
|
|
283
|
+
"input",
|
|
284
|
+
{
|
|
285
|
+
ref: bookmarkInputRef,
|
|
286
|
+
type: "text",
|
|
287
|
+
value: bookmarkDraft,
|
|
288
|
+
onChange: (e) => setBookmarkDraft(e.target.value),
|
|
289
|
+
onKeyDown: (e) => {
|
|
290
|
+
if (e.key === "Enter") commitBookmark();
|
|
291
|
+
else if (e.key === "Escape") setBookmarkDraft(null);
|
|
292
|
+
},
|
|
293
|
+
placeholder: "Name",
|
|
294
|
+
className: "w-full text-sm px-2 py-1.5 border border-gray-300 rounded focus:outline-none focus:border-blue-400 focus:ring-1 focus:ring-blue-200"
|
|
295
|
+
}
|
|
296
|
+
),
|
|
297
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1 mt-2", children: [
|
|
298
|
+
/* @__PURE__ */ jsx(
|
|
299
|
+
"button",
|
|
300
|
+
{
|
|
301
|
+
onClick: () => setBookmarkDraft(null),
|
|
302
|
+
className: "px-2 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded",
|
|
303
|
+
children: "Cancel"
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
/* @__PURE__ */ jsx(
|
|
307
|
+
"button",
|
|
308
|
+
{
|
|
309
|
+
onClick: commitBookmark,
|
|
310
|
+
disabled: !bookmarkDraft.trim(),
|
|
311
|
+
className: "px-2 py-1 text-xs bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-40",
|
|
312
|
+
children: "Save"
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
] })
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
),
|
|
319
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 px-2 py-1 border-b border-gray-200 bg-white shrink-0 overflow-x-auto", children: [
|
|
320
|
+
bookmarks.length === 0 && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-gray-400 italic px-2", children: "No bookmarks yet \u2014 star the address bar to add one." }),
|
|
321
|
+
bookmarks.map((b, i) => /* @__PURE__ */ jsxs(
|
|
322
|
+
"button",
|
|
323
|
+
{
|
|
324
|
+
onClick: () => navigate(b.url),
|
|
325
|
+
onContextMenu: async (e) => {
|
|
326
|
+
e.preventDefault();
|
|
327
|
+
const ok = await confirm({
|
|
328
|
+
title: "Remove bookmark",
|
|
329
|
+
message: `"${b.label}" will be removed from your bookmarks.`,
|
|
330
|
+
confirmLabel: "Remove",
|
|
331
|
+
variant: "danger"
|
|
332
|
+
});
|
|
333
|
+
if (ok) removeBookmark(i);
|
|
334
|
+
},
|
|
335
|
+
className: "flex items-center gap-1.5 px-2 py-0.5 rounded text-[12px] text-gray-700 hover:bg-gray-100 whitespace-nowrap",
|
|
336
|
+
title: `${b.url}
|
|
337
|
+
(right-click to remove)`,
|
|
338
|
+
children: [
|
|
339
|
+
/* @__PURE__ */ jsx(
|
|
340
|
+
"img",
|
|
341
|
+
{
|
|
342
|
+
src: `https://icons.duckduckgo.com/ip3/${new URL(b.url).hostname}.ico`,
|
|
343
|
+
alt: "",
|
|
344
|
+
className: "h-3.5 w-3.5",
|
|
345
|
+
onError: (e) => {
|
|
346
|
+
e.target.style.visibility = "hidden";
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
),
|
|
350
|
+
b.label
|
|
351
|
+
]
|
|
352
|
+
},
|
|
353
|
+
i
|
|
354
|
+
)),
|
|
355
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
356
|
+
/* @__PURE__ */ jsx(
|
|
357
|
+
"button",
|
|
358
|
+
{
|
|
359
|
+
onClick: setAsHomepage,
|
|
360
|
+
className: "px-2 py-0.5 text-[11px] text-gray-500 hover:text-gray-800",
|
|
361
|
+
title: "Set current page as homepage",
|
|
362
|
+
children: "Set as home"
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
] }),
|
|
366
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 relative min-h-0 bg-gray-50", children: /* @__PURE__ */ jsx(
|
|
367
|
+
BrowserBody,
|
|
368
|
+
{
|
|
369
|
+
url,
|
|
370
|
+
iframeKey,
|
|
371
|
+
iframeRef,
|
|
372
|
+
openExternal,
|
|
373
|
+
showHelp,
|
|
374
|
+
dismissHelp: () => setShowHelp(false)
|
|
375
|
+
}
|
|
376
|
+
) })
|
|
377
|
+
] });
|
|
378
|
+
}
|
|
379
|
+
function titleFromUrl(u) {
|
|
380
|
+
try {
|
|
381
|
+
const url = new URL(u);
|
|
382
|
+
return url.hostname.replace(/^www\./, "");
|
|
383
|
+
} catch {
|
|
384
|
+
return u;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function BrowserBody({
|
|
388
|
+
url,
|
|
389
|
+
iframeKey,
|
|
390
|
+
iframeRef,
|
|
391
|
+
openExternal,
|
|
392
|
+
showHelp,
|
|
393
|
+
dismissHelp
|
|
394
|
+
}) {
|
|
395
|
+
const [forceTry, setForceTry] = useState(false);
|
|
396
|
+
useEffect(() => {
|
|
397
|
+
setForceTry(false);
|
|
398
|
+
}, [url]);
|
|
399
|
+
const blocked = hostIsBlocked(url) && !forceTry;
|
|
400
|
+
if (blocked) {
|
|
401
|
+
return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-white p-8", children: /* @__PURE__ */ jsxs("div", { className: "max-w-md text-center", children: [
|
|
402
|
+
/* @__PURE__ */ jsx("svg", { className: "h-14 w-14 mx-auto text-gray-300 mb-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L21 21M5.636 5.636L3 3m9 9a9 9 0 110-18 9 9 0 010 18z" }) }),
|
|
403
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-base font-semibold text-gray-800 mb-1", children: [
|
|
404
|
+
titleFromUrl(url),
|
|
405
|
+
" can't be embedded"
|
|
406
|
+
] }),
|
|
407
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mb-4", children: [
|
|
408
|
+
"This site sends an ",
|
|
409
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: "X-Frame-Options" }),
|
|
410
|
+
" ",
|
|
411
|
+
"or ",
|
|
412
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: "Content-Security-Policy" }),
|
|
413
|
+
" header that refuses iframe embedding. The browser blocks the load before our app can do anything about it."
|
|
414
|
+
] }),
|
|
415
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
|
|
416
|
+
/* @__PURE__ */ jsxs(
|
|
417
|
+
"button",
|
|
418
|
+
{
|
|
419
|
+
onClick: openExternal,
|
|
420
|
+
className: "px-4 py-2 bg-blue-500 text-white text-sm rounded hover:bg-blue-600 inline-flex items-center gap-2",
|
|
421
|
+
children: [
|
|
422
|
+
/* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-7.5-9L21 3m0 0v6m0-6L9.75 14.25" }) }),
|
|
423
|
+
"Open in a new tab"
|
|
424
|
+
]
|
|
425
|
+
}
|
|
426
|
+
),
|
|
427
|
+
/* @__PURE__ */ jsx(
|
|
428
|
+
"button",
|
|
429
|
+
{
|
|
430
|
+
onClick: () => setForceTry(true),
|
|
431
|
+
className: "text-xs text-gray-500 hover:text-gray-800 underline",
|
|
432
|
+
children: "Try loading it here anyway"
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
] })
|
|
436
|
+
] }) });
|
|
437
|
+
}
|
|
438
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
439
|
+
/* @__PURE__ */ jsx(
|
|
440
|
+
"iframe",
|
|
441
|
+
{
|
|
442
|
+
ref: iframeRef,
|
|
443
|
+
src: url,
|
|
444
|
+
className: "absolute inset-0 w-full h-full bg-white",
|
|
445
|
+
sandbox: "allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals",
|
|
446
|
+
referrerPolicy: "no-referrer-when-downgrade"
|
|
447
|
+
},
|
|
448
|
+
iframeKey + url
|
|
449
|
+
),
|
|
450
|
+
showHelp && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 max-w-sm bg-white border border-gray-200 rounded-md shadow-lg p-3 text-xs text-gray-700 z-10", children: [
|
|
451
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-gray-900 mb-1", children: "Why is this page blank?" }),
|
|
452
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-2", children: [
|
|
453
|
+
"Most major sites (Google, GitHub, banks, news) refuse to be embedded in an iframe via ",
|
|
454
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "X-Frame-Options" }),
|
|
455
|
+
" or Content Security Policy. There's no workaround \u2014 the browser blocks the load before our app can do anything."
|
|
456
|
+
] }),
|
|
457
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
458
|
+
"Hit the ",
|
|
459
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "\u2197" }),
|
|
460
|
+
" button to open the page in a real new tab. Sites that ",
|
|
461
|
+
/* @__PURE__ */ jsx("em", { children: "do" }),
|
|
462
|
+
" allow embedding (Wikipedia, DevDocs, your own apps) work fine in here."
|
|
463
|
+
] }),
|
|
464
|
+
/* @__PURE__ */ jsx(
|
|
465
|
+
"button",
|
|
466
|
+
{
|
|
467
|
+
onClick: dismissHelp,
|
|
468
|
+
className: "mt-2 text-[11px] text-blue-600 hover:underline",
|
|
469
|
+
children: "Got it"
|
|
470
|
+
}
|
|
471
|
+
)
|
|
472
|
+
] })
|
|
473
|
+
] });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export { Browser as default };
|
|
477
|
+
//# sourceMappingURL=Browser-DBRSIQ4J.js.map
|
|
478
|
+
//# sourceMappingURL=Browser-DBRSIQ4J.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/apps/Browser.tsx"],"names":[],"mappings":"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","file":"Browser-DBRSIQ4J.js","sourcesContent":["/**\n * Browser — minimal iframe-backed web browser app.\n *\n * URL bar with back / forward / refresh, navigable bookmark bar\n * (persisted to localStorage), and a graceful \"open in new tab\"\n * escape hatch since most major sites refuse iframe embedding via\n * X-Frame-Options or Content-Security-Policy.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { WindowTitle } from '../shell/Modal';\nimport { confirm } from '../shell/ConfirmDialog';\nimport AboutApp from './_about';\nimport { peekBrowserStartUrl, claimBrowserStartUrl, type PendingStartUrl } from './_browserStage';\n\ninterface Bookmark {\n label: string;\n url: string;\n}\n\nconst BOOKMARKS_KEY = 'react-os-shell:browser-bookmarks';\nconst HOMEPAGE_KEY = 'react-os-shell:browser-homepage';\nconst DEFAULT_HOMEPAGE = 'https://en.wikipedia.org/wiki/Main_Page';\n// Search provider (DuckDuckGo, no tracking). Hoisted to a constant so the\n// provider is stated in exactly one place — note it is deliberately NOT\n// framable, hence its presence in BLOCKED_HOSTS below.\nconst SEARCH_URL = 'https://duckduckgo.com/?q=';\nconst DEFAULT_BOOKMARKS: Bookmark[] = [\n { label: 'Wikipedia', url: 'https://en.wikipedia.org/wiki/Main_Page' },\n // DevDocs rather than MDN: MDN sends `x-frame-options: DENY`, so shipping it\n // as a default bookmark shipped a guaranteed-blank pane (BG#00374). DevDocs\n // carries the same API reference content and permits embedding.\n { label: 'DevDocs', url: 'https://devdocs.io' },\n { label: 'Example', url: 'https://example.com' },\n];\n\n// A dotted single token is ambiguous: `example.com` is a host, but `node.js`,\n// `web.config` and `array.map` are search queries the user expects to search.\n// The only offline signal is the last label, so we treat a bare token as a host\n// ONLY when it ends in a real TLD; everything else searches. A host on an\n// exotic TLD missing from this list is still reachable by typing the scheme\n// (`https://foo.zuerich`), which short-circuits before this check.\nconst KNOWN_TLDS = new Set([\n // ISO 3166-1 alpha-2 ccTLDs. Mind the lookalikes: `md`, `py`, `rs` and `sh`\n // ARE countries (`foo.sh` is a real host), while `js` and `ts` are not.\n 'ac ad ae af ag ai al am ao aq ar as at au aw ax az ba bb bd be bf bg bh bi',\n 'bj bm bn bo br bs bt bw by bz ca cc cd cf cg ch ci ck cl cm cn co cr cu cv',\n 'cw cx cy cz de dj dk dm do dz ec ee eg er es et eu fi fj fk fm fo fr ga gd',\n 'ge gf gg gh gi gl gm gn gp gq gr gs gt gu gw gy hk hm hn hr ht hu id ie il',\n 'im in io iq ir is it je jm jo jp ke kg kh ki km kn kp kr kw ky kz la lb lc',\n 'li lk lr ls lt lu lv ly ma mc md me mg mh mk ml mm mn mo mp mq mr ms mt mu',\n 'mv mw mx my mz na nc ne nf ng ni nl no np nr nu nz om pa pe pf pg ph pk pl',\n 'pm pn pr ps pt pw py qa re ro rs ru rw sa sb sc sd se sg sh si sk sl sm sn',\n 'so sr ss st su sv sx sy sz tc td tf tg th tj tk tl tm tn to tr tt tv tw tz',\n 'ua ug uk us uy uz va vc ve vg vi vn vu wf ws ye yt za zm zw',\n // Common gTLDs, plus the ones this business actually uses (`design`).\n 'com net org edu gov mil int info biz name pro app dev xyz online site tech',\n 'store shop cloud design digital agency studio media news blog live life',\n 'world today email systems solutions services software page link space host',\n 'press wiki team group works careers finance capital energy academy',\n].join(' ').split(' '));\n\n// `example.com`, `sub.example.co.uk`, `example.com/path` → host; `node.js` → search.\nfunction isProbableHost(s: string): boolean {\n const m = /^([\\w-]+\\.)+([a-z]{2,})(\\/.*)?$/i.exec(s);\n return m !== null && KNOWN_TLDS.has(m[2].toLowerCase());\n}\n\nfunction normalizeUrl(input: string): string {\n let s = input.trim();\n if (!s) return '';\n // Already a URL? Otherwise treat as a search query.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(s)) return s;\n if (isProbableHost(s)) return 'https://' + s;\n return SEARCH_URL + encodeURIComponent(s);\n}\n\n// Sites known to refuse iframe embedding via X-Frame-Options or CSP. There's\n// no workaround inside an iframe — the browser blocks the load. We keep a\n// list so we can short-circuit to a friendly \"open in new tab\" panel\n// instead of letting the browser's blank \"refused to connect\" error\n// through. Subdomain match: `mail.google.com` matches `google.com`.\nconst BLOCKED_HOSTS = [\n // The two hosts this app itself routes to or ships as a default bookmark.\n // Their absence here was BG#00374: an address-bar search went to a blank\n // pane rather than the panel below, reading as \"search does nothing\".\n // Verified live 2026-07-16 — duckduckgo.com: `x-frame-options: SAMEORIGIN` +\n // `frame-ancestors 'self' https://html.duckduckgo.com` (the html/ and lite/\n // mirrors are no escape hatch: `frame-ancestors 'self'`);\n // developer.mozilla.org: `x-frame-options: DENY` (kept even though the\n // default bookmark moved to DevDocs — existing users have MDN persisted in\n // localStorage, and it stays a thing people type).\n 'duckduckgo.com', 'developer.mozilla.org',\n 'google.com', 'gmail.com', 'youtube.com',\n 'facebook.com', 'instagram.com', 'whatsapp.com',\n 'twitter.com', 'x.com',\n 'github.com', 'gitlab.com',\n 'linkedin.com', 'reddit.com', 'pinterest.com',\n 'amazon.com', 'amazon.ca', 'amazon.co.uk',\n 'apple.com', 'icloud.com',\n 'microsoft.com', 'outlook.com', 'live.com', 'office.com',\n 'netflix.com', 'spotify.com',\n 'paypal.com', 'stripe.com',\n 'chat.openai.com', 'chatgpt.com', 'claude.ai',\n];\n\nfunction hostIsBlocked(href: string): boolean {\n try {\n const host = new URL(href).hostname.toLowerCase();\n return BLOCKED_HOSTS.some(b => host === b || host.endsWith('.' + b));\n } catch { return false; }\n}\n\nfunction loadBookmarks(): Bookmark[] {\n if (typeof window === 'undefined') return DEFAULT_BOOKMARKS;\n try {\n const raw = localStorage.getItem(BOOKMARKS_KEY);\n if (!raw) return DEFAULT_BOOKMARKS;\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed) && parsed.every(b => typeof b?.url === 'string')) return parsed;\n } catch {}\n return DEFAULT_BOOKMARKS;\n}\n\n// The consumer-facing `setBrowserStartUrl` lives in ./_browserStage so that\n// hosts importing it don't pull this module into their startup bundle. This\n// component drains the stage via the @internal peek/claim pair.\n\nfunction loadHomepage(): string {\n if (typeof window === 'undefined') return DEFAULT_HOMEPAGE;\n return localStorage.getItem(HOMEPAGE_KEY) || DEFAULT_HOMEPAGE;\n}\n\nexport default function Browser() {\n // One-shot drain of a staged start URL. The render phase only PEEKS — under\n // React 18 concurrent rendering the first render pass of this lazy component\n // can be discarded and replayed, so a destructive read here would lose the\n // payload (see Spreadsheet/Preview). The stage is cleared in the mount\n // effect below.\n const consumedRef = useRef<PendingStartUrl | null | undefined>(undefined);\n if (consumedRef.current === undefined) {\n consumedRef.current = peekBrowserStartUrl();\n }\n useEffect(() => {\n if (consumedRef.current != null) {\n claimBrowserStartUrl(consumedRef.current);\n }\n }, []);\n const startUrl = consumedRef.current ? normalizeUrl(consumedRef.current.url) : '';\n\n const [homepage, setHomepage] = useState(loadHomepage);\n const [url, setUrl] = useState(startUrl || homepage);\n const [inputUrl, setInputUrl] = useState(url);\n const [history, setHistory] = useState<string[]>([url]);\n const [historyIdx, setHistoryIdx] = useState(0);\n const [iframeKey, setIframeKey] = useState(0);\n const [bookmarks, setBookmarks] = useState<Bookmark[]>(loadBookmarks);\n const [showHelp, setShowHelp] = useState(false);\n // Inline \"name this bookmark\" popover state. Populated when the user\n // clicks the star icon to add — replaces the native window.prompt().\n const [bookmarkDraft, setBookmarkDraft] = useState<string | null>(null);\n const bookmarkInputRef = useRef<HTMLInputElement>(null);\n const iframeRef = useRef<HTMLIFrameElement>(null);\n\n // Persist bookmarks.\n useEffect(() => {\n try { localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(bookmarks)); } catch {}\n }, [bookmarks]);\n\n const navigate = (next: string) => {\n const n = normalizeUrl(next);\n if (!n) return;\n setUrl(n);\n setInputUrl(n);\n // Truncate forward history when navigating from a back state.\n setHistory(h => {\n const trimmed = h.slice(0, historyIdx + 1);\n trimmed.push(n);\n return trimmed;\n });\n setHistoryIdx(i => i + 1);\n setShowHelp(false);\n };\n\n const back = () => {\n if (historyIdx > 0) {\n const next = historyIdx - 1;\n setHistoryIdx(next);\n setUrl(history[next]);\n setInputUrl(history[next]);\n setShowHelp(false);\n }\n };\n\n const forward = () => {\n if (historyIdx < history.length - 1) {\n const next = historyIdx + 1;\n setHistoryIdx(next);\n setUrl(history[next]);\n setInputUrl(history[next]);\n setShowHelp(false);\n }\n };\n\n const refresh = () => setIframeKey(k => k + 1);\n\n const goHome = () => navigate(homepage);\n\n const openExternal = () => {\n if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener,noreferrer');\n };\n\n const openBookmarkPopover = () => {\n setBookmarkDraft(titleFromUrl(url));\n // Focus + select happens in a useEffect once the input is mounted.\n };\n\n const commitBookmark = () => {\n const label = bookmarkDraft?.trim();\n if (!label) { setBookmarkDraft(null); return; }\n setBookmarks(b => [...b, { label, url }]);\n setBookmarkDraft(null);\n };\n\n // Auto-focus + select the popover input when it opens, and dismiss on\n // outside click.\n useEffect(() => {\n if (bookmarkDraft === null) return;\n bookmarkInputRef.current?.focus();\n bookmarkInputRef.current?.select();\n const onDocClick = (e: MouseEvent) => {\n const target = e.target as HTMLElement | null;\n if (!target?.closest('[data-bookmark-popover]')) setBookmarkDraft(null);\n };\n // Defer one tick so the click that opened the popover doesn't close it.\n const t = setTimeout(() => document.addEventListener('mousedown', onDocClick), 0);\n return () => {\n clearTimeout(t);\n document.removeEventListener('mousedown', onDocClick);\n };\n }, [bookmarkDraft !== null]);\n\n const removeBookmark = (i: number) => {\n setBookmarks(b => b.filter((_, idx) => idx !== i));\n };\n\n const setAsHomepage = () => {\n setHomepage(url);\n try { localStorage.setItem(HOMEPAGE_KEY, url); } catch {}\n };\n\n const onSubmitUrl = (e: React.FormEvent) => {\n e.preventDefault();\n navigate(inputUrl);\n };\n\n const canBack = historyIdx > 0;\n const canForward = historyIdx < history.length - 1;\n const isBookmarked = bookmarks.some(b => b.url === url);\n const btn = 'p-1.5 rounded hover:bg-gray-200 transition-colors text-gray-600 disabled:opacity-30';\n\n return (\n <div className=\"relative flex flex-col h-full bg-white\">\n <WindowTitle title={`Browser - ${titleFromUrl(url)}`} />\n <AboutApp app=\"browser\" />\n\n {/* Top toolbar */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0\">\n <button onClick={back} disabled={!canBack} className={btn} title=\"Back\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" /></svg>\n </button>\n <button onClick={forward} disabled={!canForward} className={btn} title=\"Forward\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" /></svg>\n </button>\n <button onClick={refresh} className={btn} title=\"Refresh\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.8}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992M3.05 9.348a9 9 0 0114.85-3.36L21.015 9.348m0 5.304a9 9 0 01-14.85 3.36l-3.115-3.36\" /></svg>\n </button>\n <button onClick={goHome} className={btn} title=\"Home\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.8}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M2.25 12l8.954-8.955a1.5 1.5 0 012.122 0l8.954 8.955M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25\" /></svg>\n </button>\n\n <form onSubmit={onSubmitUrl} className=\"flex-1 flex items-center mx-1\">\n <div className=\"flex items-center w-full bg-white border border-gray-300 rounded focus-within:border-blue-400 focus-within:ring-1 focus-within:ring-blue-200\">\n <span className=\"px-2 text-gray-400 text-xs\">{url.startsWith('https://') ? '🔒' : '⚠️'}</span>\n <input\n type=\"text\"\n value={inputUrl}\n onChange={(e) => setInputUrl(e.target.value)}\n onFocus={(e) => e.target.select()}\n className=\"flex-1 px-1 py-1 text-sm bg-transparent outline-none font-mono\"\n spellCheck={false}\n placeholder=\"Enter URL or search…\"\n />\n {url !== inputUrl && (\n <button\n type=\"button\"\n onClick={() => setInputUrl(url)}\n className=\"px-1.5 text-[10px] text-gray-400 hover:text-gray-700\"\n title=\"Reset to current URL\"\n >×</button>\n )}\n </div>\n </form>\n\n <button\n data-bookmark-popover\n onClick={() => isBookmarked\n ? removeBookmark(bookmarks.findIndex(b => b.url === url))\n : openBookmarkPopover()}\n className={btn + ' ' + (isBookmarked ? 'text-yellow-500' : '')}\n title={isBookmarked ? 'Remove bookmark' : 'Add bookmark'}\n >\n <svg className=\"h-4 w-4\" fill={isBookmarked ? 'currentColor' : 'none'} viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z\" />\n </svg>\n </button>\n <button onClick={openExternal} className={btn} title=\"Open in new tab\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-7.5-9L21 3m0 0v6m0-6L9.75 14.25\" /></svg>\n </button>\n <button onClick={() => setShowHelp(s => !s)} className={btn} title=\"Embedding help\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z\" /></svg>\n </button>\n </div>\n\n {/* Inline \"name this bookmark\" popover. Anchored to the right of the\n toolbar (where the star icon lives) so the visual flow matches\n how the user just clicked. Renders inside the panel so it\n inherits the window's z-stacking. */}\n {bookmarkDraft !== null && (\n <div\n data-bookmark-popover\n className=\"absolute right-3 top-12 z-30 w-72 bg-white border border-gray-200 rounded-md shadow-lg p-3\"\n >\n <div className=\"text-[11px] font-semibold uppercase tracking-wide text-gray-500 mb-1\">Bookmark this page</div>\n <div className=\"text-[11px] text-gray-400 truncate mb-2 font-mono\" title={url}>{url}</div>\n <input\n ref={bookmarkInputRef}\n type=\"text\"\n value={bookmarkDraft}\n onChange={(e) => setBookmarkDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') commitBookmark();\n else if (e.key === 'Escape') setBookmarkDraft(null);\n }}\n placeholder=\"Name\"\n className=\"w-full text-sm px-2 py-1.5 border border-gray-300 rounded focus:outline-none focus:border-blue-400 focus:ring-1 focus:ring-blue-200\"\n />\n <div className=\"flex justify-end gap-1 mt-2\">\n <button\n onClick={() => setBookmarkDraft(null)}\n className=\"px-2 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded\"\n >Cancel</button>\n <button\n onClick={commitBookmark}\n disabled={!bookmarkDraft.trim()}\n className=\"px-2 py-1 text-xs bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-40\"\n >Save</button>\n </div>\n </div>\n )}\n\n {/* Bookmarks bar */}\n <div className=\"flex items-center gap-0.5 px-2 py-1 border-b border-gray-200 bg-white shrink-0 overflow-x-auto\">\n {bookmarks.length === 0 && (\n <span className=\"text-[11px] text-gray-400 italic px-2\">No bookmarks yet — star the address bar to add one.</span>\n )}\n {bookmarks.map((b, i) => (\n <button\n key={i}\n onClick={() => navigate(b.url)}\n onContextMenu={async (e) => {\n e.preventDefault();\n const ok = await confirm({\n title: 'Remove bookmark',\n message: `\"${b.label}\" will be removed from your bookmarks.`,\n confirmLabel: 'Remove',\n variant: 'danger',\n });\n if (ok) removeBookmark(i);\n }}\n className=\"flex items-center gap-1.5 px-2 py-0.5 rounded text-[12px] text-gray-700 hover:bg-gray-100 whitespace-nowrap\"\n title={`${b.url}\\n(right-click to remove)`}\n >\n <img\n src={`https://icons.duckduckgo.com/ip3/${new URL(b.url).hostname}.ico`}\n alt=\"\"\n className=\"h-3.5 w-3.5\"\n onError={(e) => { (e.target as HTMLImageElement).style.visibility = 'hidden'; }}\n />\n {b.label}\n </button>\n ))}\n <div className=\"flex-1\" />\n <button\n onClick={setAsHomepage}\n className=\"px-2 py-0.5 text-[11px] text-gray-500 hover:text-gray-800\"\n title=\"Set current page as homepage\"\n >Set as home</button>\n </div>\n\n {/* Iframe area */}\n <div className=\"flex-1 relative min-h-0 bg-gray-50\">\n <BrowserBody\n url={url}\n iframeKey={iframeKey}\n iframeRef={iframeRef}\n openExternal={openExternal}\n showHelp={showHelp}\n dismissHelp={() => setShowHelp(false)}\n />\n </div>\n </div>\n );\n}\n\nfunction titleFromUrl(u: string): string {\n try {\n const url = new URL(u);\n return url.hostname.replace(/^www\\./, '');\n } catch {\n return u;\n }\n}\n\nfunction BrowserBody({\n url,\n iframeKey,\n iframeRef,\n openExternal,\n showHelp,\n dismissHelp,\n}: {\n url: string;\n iframeKey: number;\n iframeRef: React.RefObject<HTMLIFrameElement | null>;\n openExternal: () => void;\n showHelp: boolean;\n dismissHelp: () => void;\n}) {\n // If the user dismisses the blocked-site panel, allow them to attempt\n // the iframe load anyway. Reset the override whenever URL changes.\n const [forceTry, setForceTry] = useState(false);\n useEffect(() => { setForceTry(false); }, [url]);\n\n const blocked = hostIsBlocked(url) && !forceTry;\n\n if (blocked) {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center bg-white p-8\">\n <div className=\"max-w-md text-center\">\n <svg className=\"h-14 w-14 mx-auto text-gray-300 mb-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L21 21M5.636 5.636L3 3m9 9a9 9 0 110-18 9 9 0 010 18z\" />\n </svg>\n <h3 className=\"text-base font-semibold text-gray-800 mb-1\">\n {titleFromUrl(url)} can't be embedded\n </h3>\n <p className=\"text-sm text-gray-500 mb-4\">\n This site sends an <span className=\"font-mono text-xs\">X-Frame-Options</span>{' '}\n or <span className=\"font-mono text-xs\">Content-Security-Policy</span> header\n that refuses iframe embedding. The browser blocks the load before our app\n can do anything about it.\n </p>\n <div className=\"flex flex-col items-center gap-2\">\n <button\n onClick={openExternal}\n className=\"px-4 py-2 bg-blue-500 text-white text-sm rounded hover:bg-blue-600 inline-flex items-center gap-2\"\n >\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-7.5-9L21 3m0 0v6m0-6L9.75 14.25\" />\n </svg>\n Open in a new tab\n </button>\n <button\n onClick={() => setForceTry(true)}\n className=\"text-xs text-gray-500 hover:text-gray-800 underline\"\n >\n Try loading it here anyway\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <>\n <iframe\n key={iframeKey + url}\n ref={iframeRef as React.RefObject<HTMLIFrameElement>}\n src={url}\n className=\"absolute inset-0 w-full h-full bg-white\"\n // Sandboxing keeps embedded pages from messing with the parent\n // window state. allow-same-origin lets sites that *do* allow\n // embedding actually behave normally; allow-scripts is needed\n // for any modern site.\n sandbox=\"allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals\"\n referrerPolicy=\"no-referrer-when-downgrade\"\n />\n\n {showHelp && (\n <div className=\"absolute top-2 right-2 max-w-sm bg-white border border-gray-200 rounded-md shadow-lg p-3 text-xs text-gray-700 z-10\">\n <div className=\"font-medium text-gray-900 mb-1\">Why is this page blank?</div>\n <p className=\"mb-2\">\n Most major sites (Google, GitHub, banks, news) refuse to be embedded in\n an iframe via <span className=\"font-mono\">X-Frame-Options</span> or\n Content Security Policy. There's no workaround — the browser blocks the\n load before our app can do anything.\n </p>\n <p>\n Hit the <span className=\"font-medium\">↗</span> button to open the page in\n a real new tab. Sites that <em>do</em> allow embedding (Wikipedia, DevDocs,\n your own apps) work fine in here.\n </p>\n <button\n onClick={dismissHelp}\n className=\"mt-2 text-[11px] text-blue-600 hover:underline\"\n >Got it</button>\n </div>\n )}\n </>\n );\n}\n"]}
|