gwchq-textjam 0.3.1 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.js +288 -201
  2. package/package.json +5 -9
package/dist/index.js CHANGED
@@ -368644,7 +368644,7 @@ const editorListener_1 = __webpack_require__(41824);
368644
368644
  const Loading_1 = __importDefault(__webpack_require__(34466));
368645
368645
  const LeaveGuardController_1 = __webpack_require__(65240);
368646
368646
  (0, editorListener_1.registerEditorListeners)();
368647
- const TextJamEditor = ({ onLogoutClick = () => { }, onViewProfileClick = () => { }, onHomeClick = () => { }, saveProject, isSaving, projectContent = null, template, shareLinks = null, navigationGuardCoordinator, ...componentProps }) => {
368647
+ const TextJamEditor = ({ onLogoutClick = () => { }, onViewProfileClick = () => { }, onHomeClick = () => { }, saveProject, isSaving, projectContent = null, starterCodeContent = null, template, shareLinks = null, navigationGuardCoordinator, ...componentProps }) => {
368648
368648
  // Default props that match the previous web-component defaults
368649
368649
  const defaultProps = {
368650
368650
  sidebarOptions: [
@@ -368665,6 +368665,7 @@ const TextJamEditor = ({ onLogoutClick = () => { }, onViewProfileClick = () => {
368665
368665
  saveProject,
368666
368666
  isSaving,
368667
368667
  projectContent,
368668
+ starterCodeContent,
368668
368669
  template,
368669
368670
  shareLinks,
368670
368671
  };
@@ -369196,6 +369197,7 @@ const node_html_parser_1 = __webpack_require__(36192);
369196
369197
  const classnames_1 = __importDefault(__webpack_require__(46942));
369197
369198
  const react_router_dom_1 = __webpack_require__(92648);
369198
369199
  const EditorSlice_1 = __webpack_require__(68512);
369200
+ const externalLinkHelper_1 = __webpack_require__(31615);
369199
369201
  const open_in_new_tab_svg_1 = __importDefault(__webpack_require__(86936));
369200
369202
  const stores_1 = __webpack_require__(32132);
369201
369203
  const ProjectTypes_1 = __webpack_require__(27130);
@@ -369206,7 +369208,6 @@ const preview_svg_1 = __importDefault(__webpack_require__(80417));
369206
369208
  const OutputTabPanel_1 = __webpack_require__(25931);
369207
369209
  const styles_module_scss_1 = __importDefault(__webpack_require__(12914));
369208
369210
  const helpers_1 = __webpack_require__(1108);
369209
- const projectPath_1 = __webpack_require__(95506);
369210
369211
  const fileParsers_1 = __webpack_require__(26683);
369211
369212
  const scripts_1 = __webpack_require__(57621);
369212
369213
  const Errors_1 = __webpack_require__(20339);
@@ -369222,8 +369223,6 @@ const getConsoleKey = (message) => JSON.stringify({
369222
369223
  method: message.method,
369223
369224
  data: message.data,
369224
369225
  });
369225
- // A standalone preview view is flagged by `?preview=1` in the URL.
369226
- const isPreviewSearch = (params) => params.get("preview") === "1";
369227
369226
  function HtmlRunner() {
369228
369227
  const [consoleLogs, setConsoleLogs] = (0, react_1.useState)([]);
369229
369228
  const project = (0, stores_1.useAppSelector)((state) => state.editor.project);
@@ -369236,18 +369235,9 @@ function HtmlRunner() {
369236
369235
  const isPreviewMode = (0, stores_1.useAppSelector)((state) => state.editor.isOutputOnly);
369237
369236
  const dispatch = (0, react_redux_1.useDispatch)();
369238
369237
  const output = (0, react_1.useRef)(null);
369239
- const [searchParams] = (0, react_router_dom_1.useSearchParams)();
369238
+ const [searchParams, setSearchParams] = (0, react_router_dom_1.useSearchParams)();
369240
369239
  // Using BroadcastChannel to communicate between the main app and the preview tab
369241
369240
  const broadcastChannel = (0, react_1.useRef)(null);
369242
- const shouldUseBrowserHistoryRef = (0, react_1.useRef)(false);
369243
- const currentPageParam = searchParams.get("page");
369244
- const isPreviewUrl = isPreviewSearch(searchParams);
369245
- // In the standalone preview tab (and any ?preview=1 URL) the browser's
369246
- // back/forward/refresh buttons must drive page navigation.
369247
- const shouldUseBrowserHistory = isPreviewMode || isPreviewUrl;
369248
- (0, react_1.useEffect)(() => {
369249
- shouldUseBrowserHistoryRef.current = shouldUseBrowserHistory;
369250
- }, [shouldUseBrowserHistory]);
369251
369241
  (0, react_1.useEffect)(() => {
369252
369242
  broadcastChannel.current = new BroadcastChannel(BROADCAST_CHANNEL);
369253
369243
  return () => {
@@ -369267,58 +369257,18 @@ function HtmlRunner() {
369267
369257
  defaultPreviewFilePath = page;
369268
369258
  }
369269
369259
  const [runningFilePath, setRunningFilePath] = (0, react_1.useState)(defaultPreviewFilePath);
369270
- const writePreviewUrlToHistory = (path, replace = false) => {
369271
- const normalizedPath = (0, projectPath_1.normalizePath)(path);
369272
- const url = new URL(window.location.href);
369273
- if (url.searchParams.get("page") === normalizedPath) {
369274
- return false;
369275
- }
369276
- url.searchParams.set("preview", "1");
369277
- url.searchParams.set("page", normalizedPath);
369278
- if (replace) {
369279
- window.history.replaceState({ __textJamPreview: true, page: normalizedPath }, "", url.toString());
369280
- }
369281
- else {
369282
- window.history.pushState({ __textJamPreview: true, page: normalizedPath }, "", url.toString());
369283
- }
369284
- return true;
369285
- };
369286
- // Sync the initial preview page into the URL and kick off the first run.
369287
369260
  (0, react_1.useEffect)(() => {
369288
- if (!shouldUseBrowserHistory)
369289
- return;
369290
- const url = new URL(window.location.href);
369291
- const pageFromUrl = url.searchParams.get("page");
369292
- const initialPage = (0, projectPath_1.normalizePath)(pageFromUrl ?? page ?? helpers_1.DEFAULT_ENTRY_FILE_PATH);
369293
- if (!pageFromUrl) {
369294
- writePreviewUrlToHistory(initialPage, true);
369295
- }
369296
- else {
369297
- const normalizedUrl = new URL(window.location.href);
369298
- normalizedUrl.searchParams.set("page", initialPage);
369299
- window.history.replaceState({ __textJamPreview: true, page: initialPage }, "", normalizedUrl.toString());
369300
- }
369301
- dispatch((0, EditorSlice_1.setPage)(initialPage));
369302
- dispatch((0, EditorSlice_1.triggerCodeRun)());
369303
- }, [shouldUseBrowserHistory]);
369304
- // Browser back/forward: re-run the page encoded in the URL.
369305
- (0, react_1.useEffect)(() => {
369306
- if (!shouldUseBrowserHistory)
369307
- return;
369308
- const handlePopState = () => {
369309
- const url = new URL(window.location.href);
369310
- const pageFromUrl = url.searchParams.get("page");
369311
- if (!pageFromUrl)
369312
- return;
369313
- const normalizedPage = (0, projectPath_1.normalizePath)(pageFromUrl);
369314
- dispatch((0, EditorSlice_1.setPage)(normalizedPage));
369261
+ if (isPreviewMode) {
369315
369262
  dispatch((0, EditorSlice_1.triggerCodeRun)());
369316
- };
369317
- window.addEventListener("popstate", handlePopState);
369318
- return () => {
369319
- window.removeEventListener("popstate", handlePopState);
369320
- };
369321
- }, [shouldUseBrowserHistory]);
369263
+ if (page) {
369264
+ setSearchParams((prevParams) => {
369265
+ const updatedParams = new URLSearchParams(prevParams);
369266
+ updatedParams.set("page", page);
369267
+ return updatedParams;
369268
+ }, { replace: true });
369269
+ }
369270
+ }
369271
+ }, [isPreviewMode, page, setSearchParams]);
369322
369272
  (0, react_1.useEffect)(() => {
369323
369273
  if (isPreviewMode) {
369324
369274
  const handleMessage = (event) => {
@@ -369333,29 +369283,29 @@ function HtmlRunner() {
369333
369283
  };
369334
369284
  }
369335
369285
  }, [isPreviewMode]);
369336
- (0, react_1.useEffect)(() => {
369337
- const handleReloadMessage = (event) => {
369338
- if (event.data?.msg !== "RELOAD")
369339
- return;
369340
- const nextPage = event.data?.payload?.linkTo;
369341
- if (typeof nextPage !== "string")
369342
- return;
369343
- const normalizedPage = (0, projectPath_1.normalizePath)(nextPage);
369344
- // Push the navigation onto the browser history stack so the native
369345
- // back/forward buttons (and refresh) navigate preview pages.
369346
- if (shouldUseBrowserHistoryRef.current ||
369347
- isPreviewSearch(new URL(window.location.href).searchParams)) {
369348
- writePreviewUrlToHistory(normalizedPage);
369349
- }
369350
- dispatch((0, EditorSlice_1.setPage)(normalizedPage));
369351
- dispatch((0, EditorSlice_1.triggerCodeRun)());
369352
- };
369353
- window.addEventListener("message", handleReloadMessage);
369354
- return () => window.removeEventListener("message", handleReloadMessage);
369355
- }, [dispatch]);
369286
+ const showModal = () => {
369287
+ dispatch((0, EditorSlice_1.showErrorModal)());
369288
+ eventListener();
369289
+ };
369290
+ const { externalLink, setExternalLink, handleAllowedExternalLink, handleRegularExternalLink, handleExternalLinkError, } = (0, externalLinkHelper_1.useExternalLinkState)(showModal);
369291
+ const eventListener = () => {
369292
+ window.addEventListener("message", (event) => {
369293
+ if (typeof event.data?.msg === "string") {
369294
+ if (event.data?.msg === "ERROR: External link") {
369295
+ handleExternalLinkError();
369296
+ }
369297
+ else if (event.data?.msg === "Allowed external link") {
369298
+ handleAllowedExternalLink(event.data.payload.linkTo);
369299
+ }
369300
+ else {
369301
+ handleRegularExternalLink(event.data.payload.linkTo);
369302
+ }
369303
+ }
369304
+ });
369305
+ };
369356
369306
  const iframeReload = () => {
369357
369307
  const iframe = output.current?.contentDocument;
369358
- const filePath = (0, helpers_1.getFilenameFromIFrame)(iframe);
369308
+ const filePath = (0, helpers_1.getFilenameFromIFrame)(iframe) ?? externalLink;
369359
369309
  if (runningFilePath !== filePath) {
369360
369310
  setRunningFilePath(filePath);
369361
369311
  }
@@ -369365,11 +369315,9 @@ function HtmlRunner() {
369365
369315
  linkElement.addEventListener("click", (e) => {
369366
369316
  const href = linkElement.getAttribute("href");
369367
369317
  const target = linkElement.getAttribute("target");
369368
- if (!href || target !== "_blank" || (0, helpers_1.isExternalUrl)(href))
369369
- return;
369370
- // block in-iframe navigation for internal html links with target="_blank"
369318
+ // block in-iframe navigation for links with target="_blank" and .html href
369371
369319
  // and open them in a preview tab
369372
- if (href?.includes(".html")) {
369320
+ if (target === "_blank" && href?.includes(".html")) {
369373
369321
  e.preventDefault();
369374
369322
  e.stopImmediatePropagation();
369375
369323
  openPreview(`/${href}`);
@@ -369377,8 +369325,10 @@ function HtmlRunner() {
369377
369325
  }, true);
369378
369326
  });
369379
369327
  }
369328
+ setExternalLink(null);
369380
369329
  };
369381
369330
  (0, react_1.useEffect)(() => {
369331
+ eventListener();
369382
369332
  dispatch((0, EditorSlice_1.loadingRunner)(EditorTypes_1.RunnerType.HTML));
369383
369333
  dispatch((0, EditorSlice_1.setLoadedRunner)(EditorTypes_1.RunnerType.HTML));
369384
369334
  }, []);
@@ -369392,45 +369342,50 @@ function HtmlRunner() {
369392
369342
  }
369393
369343
  }
369394
369344
  }, [codeRunTriggered, page, isPreviewMode]);
369345
+ const currentPageParam = searchParams.get("page");
369395
369346
  (0, react_1.useEffect)(() => {
369396
- if (!currentPageParam)
369397
- return;
369398
- // In preview mode page changes are driven by the browser history flow
369399
- // (popstate); in regular editor mode keep Redux page in sync with the URL.
369400
- if (shouldUseBrowserHistory)
369401
- return;
369402
- dispatch((0, EditorSlice_1.setPage)(currentPageParam));
369403
- }, [currentPageParam, shouldUseBrowserHistory, dispatch]);
369347
+ if (currentPageParam) {
369348
+ dispatch((0, EditorSlice_1.setPage)(currentPageParam));
369349
+ }
369350
+ }, [currentPageParam, dispatch]);
369404
369351
  const runCode = async () => {
369405
369352
  setConsoleLogs([]);
369406
369353
  const fileToRun = page ?? defaultPreviewFilePath;
369407
369354
  setRunningFilePath(fileToRun);
369408
369355
  dispatch((0, EditorSlice_1.setError)(null));
369409
- const entryPoint = (0, helpers_1.getEntryPoint)(projectComponents, fileToRun);
369410
- if (!entryPoint) {
369411
- dispatch((0, EditorSlice_1.setError)(fileToRun === helpers_1.DEFAULT_ENTRY_FILE_PATH && !isPreviewMode
369412
- ? {
369413
- type: Errors_1.ErrorType.ENTRY_FILE_NOT_FOUND,
369414
- details: {
369415
- fileName: helpers_1.DEFAULT_ENTRY_FILE_NAME,
369416
- },
369417
- }
369418
- : {
369419
- type: Errors_1.ErrorType.PAGE_NOT_FOUND,
369420
- }));
369421
- dispatch((0, EditorSlice_1.codeRunHandled)());
369422
- return;
369423
- }
369424
- const indexPage = (0, node_html_parser_1.parse)(entryPoint.content);
369425
- const body = indexPage.querySelector("body") || indexPage;
369426
- const htmlRoot = indexPage.querySelector("html") ?? indexPage;
369427
- body.insertAdjacentHTML("afterbegin", scripts_1.disableLocalStorageScript);
369428
- htmlRoot.insertAdjacentHTML("afterbegin", scripts_1.consoleOverrideScript);
369429
- const { content } = await (0, fileParsers_1.resolveAndRewriteHtmlImports)(indexPage.toString(), projectComponents, entryPoint.path, page ?? entryPoint.path);
369430
- if (output.current) {
369431
- output.current.srcdoc = content;
369356
+ if (!externalLink) {
369357
+ const entryPoint = (0, helpers_1.getEntryPoint)(projectComponents, fileToRun);
369358
+ if (!entryPoint) {
369359
+ dispatch((0, EditorSlice_1.setError)(fileToRun === helpers_1.DEFAULT_ENTRY_FILE_PATH && !isPreviewMode
369360
+ ? {
369361
+ type: Errors_1.ErrorType.ENTRY_FILE_NOT_FOUND,
369362
+ details: {
369363
+ fileName: helpers_1.DEFAULT_ENTRY_FILE_NAME,
369364
+ },
369365
+ }
369366
+ : {
369367
+ type: Errors_1.ErrorType.PAGE_NOT_FOUND,
369368
+ }));
369369
+ dispatch((0, EditorSlice_1.codeRunHandled)());
369370
+ return;
369371
+ }
369372
+ const indexPage = (0, node_html_parser_1.parse)(entryPoint.content);
369373
+ const body = indexPage.querySelector("body") || indexPage;
369374
+ const htmlRoot = indexPage.querySelector("html") ?? indexPage;
369375
+ body.insertAdjacentHTML("afterbegin", scripts_1.disableLocalStorageScript);
369376
+ htmlRoot.insertAdjacentHTML("afterbegin", scripts_1.consoleOverrideScript);
369377
+ const { content } = await (0, fileParsers_1.resolveAndRewriteHtmlImports)(indexPage.toString(), projectComponents, entryPoint.path, page ?? entryPoint.path);
369378
+ if (output.current) {
369379
+ output.current.srcdoc = content;
369380
+ }
369381
+ if (codeRunTriggered) {
369382
+ dispatch((0, EditorSlice_1.codeRunHandled)());
369383
+ }
369432
369384
  }
369433
- if (codeRunTriggered) {
369385
+ else {
369386
+ if (output.current) {
369387
+ output.current.src = externalLink;
369388
+ }
369434
369389
  dispatch((0, EditorSlice_1.codeRunHandled)());
369435
369390
  }
369436
369391
  };
@@ -369470,7 +369425,7 @@ function HtmlRunner() {
369470
369425
  const iframeClasses = (0, classnames_1.default)(styles_module_scss_1.default.iframe, {
369471
369426
  [styles_module_scss_1.default.codeHasBeenRun]: codeHasBeenRun,
369472
369427
  });
369473
- return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.htmlrunnerContainer, children: [(0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Preview", icon: preview_svg_1.default, readOnly: readOnly, extraTabContent: codeHasBeenRun && openInNewTabLink, tabPanelClassName: styles_module_scss_1.default.previewHtml, children: error ? ((0, jsx_runtime_1.jsx)("div", { className: iframeClasses, children: (0, jsx_runtime_1.jsx)(NotFoundPage_1.NotFoundPage, {}) })) : ((0, jsx_runtime_1.jsx)("iframe", { className: iframeClasses, sandbox: "allow-scripts allow-same-origin allow-modals allow-popups allow-popups-to-escape-sandbox", referrerPolicy: "strict-origin-when-cross-origin", allow: "\r\n accelerometer 'none';\r\n camera 'none';\r\n encrypted-media;\r\n fullscreen;\r\n picture-in-picture;\r\n geolocation 'none';\r\n gyroscope 'none';\r\n magnetometer 'none';\r\n microphone 'none';\r\n midi 'none';\r\n payment 'none';\r\n usb 'none';\r\n ", id: "output-frame", title: "HTML Output Preview", ref: output, onLoad: iframeReload })) }), !isPreviewMode && ((0, jsx_runtime_1.jsx)(ResizableWithHandle_1.default, { "data-testid": "proj-console-container", handleDirection: "top", defaultHeight: "50%", className: styles_module_scss_1.default.resizeContainer, handleClassName: styles_module_scss_1.default.resizeHandleContainer, children: (0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Console", icon: console_svg_1.default, readOnly: readOnly, children: (0, jsx_runtime_1.jsx)(HtmlConsole_1.default, { consoleLogs: consoleLogs }) }) }))] }));
369428
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.htmlrunnerContainer, children: [(0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Preview", icon: preview_svg_1.default, readOnly: readOnly, extraTabContent: codeHasBeenRun && openInNewTabLink, tabPanelClassName: styles_module_scss_1.default.previewHtml, children: error ? ((0, jsx_runtime_1.jsx)("div", { className: iframeClasses, children: (0, jsx_runtime_1.jsx)(NotFoundPage_1.NotFoundPage, {}) })) : ((0, jsx_runtime_1.jsx)("iframe", { className: iframeClasses, sandbox: "allow-scripts allow-same-origin allow-modals allow-popups", referrerPolicy: "strict-origin-when-cross-origin", allow: "\r\n accelerometer 'none';\r\n camera 'none';\r\n encrypted-media;\r\n fullscreen;\r\n picture-in-picture;\r\n geolocation 'none';\r\n gyroscope 'none';\r\n magnetometer 'none';\r\n microphone 'none';\r\n midi 'none';\r\n payment 'none';\r\n usb 'none';\r\n ", id: "output-frame", title: "HTML Output Preview", ref: output, onLoad: iframeReload })) }), !isPreviewMode && ((0, jsx_runtime_1.jsx)(ResizableWithHandle_1.default, { "data-testid": "proj-console-container", handleDirection: "top", defaultHeight: "50%", className: styles_module_scss_1.default.resizeContainer, handleClassName: styles_module_scss_1.default.resizeHandleContainer, children: (0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Console", icon: console_svg_1.default, readOnly: readOnly, children: (0, jsx_runtime_1.jsx)(HtmlConsole_1.default, { consoleLogs: consoleLogs }) }) }))] }));
369474
369429
  }
369475
369430
  exports["default"] = HtmlRunner;
369476
369431
 
@@ -369549,7 +369504,6 @@ const node_html_parser_1 = __webpack_require__(36192);
369549
369504
  const ProjectTypes_1 = __webpack_require__(27130);
369550
369505
  const binaryStore_1 = __webpack_require__(5060);
369551
369506
  const projectHelpers_1 = __webpack_require__(2610);
369552
- const helpers_1 = __webpack_require__(1108);
369553
369507
  /** Normalizes CSS paths
369554
369508
  * Treats paths without leading / or ./ or ../ as relative to current file
369555
369509
  * E.g. 'image.png' will be treated as './image.png'
@@ -369603,7 +369557,9 @@ const getMimeType = (filePath, fallback = "application/octet-stream") => {
369603
369557
  };
369604
369558
  /** Check if a URL is external, blob, or data */
369605
369559
  const isExternalOrDataUrl = (url) => {
369606
- return (url.startsWith("blob:") || (0, helpers_1.isExternalUrl)(url) || url.startsWith("data:"));
369560
+ return (url.startsWith("blob:") ||
369561
+ /^(https?:)?\/\//.test(url) ||
369562
+ url.startsWith("data:"));
369607
369563
  };
369608
369564
  async function replaceAsync(input, regex, replacer) {
369609
369565
  let result = "";
@@ -369780,12 +369736,7 @@ async function rewriteSources(indexPage, components, baseFilePath, propName, blo
369780
369736
  const nodes = indexPage.querySelectorAll(`[${propName}]`);
369781
369737
  for (const node of nodes) {
369782
369738
  const raw = node.getAttribute(propName);
369783
- if (!raw)
369784
- continue;
369785
- // For external links with target="_blank", add noopener and noreferrer for security
369786
- if ((0, helpers_1.isExternalUrl)(raw) && node.getAttribute("target") === "_blank")
369787
- node.setAttribute("rel", "noopener noreferrer");
369788
- if (isExternalOrDataUrl(raw))
369739
+ if (!raw || isExternalOrDataUrl(raw))
369789
369740
  continue;
369790
369741
  if ((0, projectHelpers_1.parseFileName)(raw).extension === ProjectTypes_1.ProjectFileExtension.HTML &&
369791
369742
  propName === "href") {
@@ -369838,7 +369789,7 @@ async function resolveAndRewriteHtmlImports(html, projectComponents, baseFilePat
369838
369789
 
369839
369790
 
369840
369791
  Object.defineProperty(exports, "__esModule", ({ value: true }));
369841
- exports.isExternalUrl = exports.getFilenameFromIFrame = exports.getEntryPoint = exports.DEFAULT_ENTRY_FILE_PATH = exports.DEFAULT_ENTRY_FILE_NAME = void 0;
369792
+ exports.getFilenameFromIFrame = exports.getEntryPoint = exports.DEFAULT_ENTRY_FILE_PATH = exports.DEFAULT_ENTRY_FILE_NAME = void 0;
369842
369793
  const ProjectTypes_1 = __webpack_require__(27130);
369843
369794
  exports.DEFAULT_ENTRY_FILE_NAME = "index.html";
369844
369795
  exports.DEFAULT_ENTRY_FILE_PATH = `/${exports.DEFAULT_ENTRY_FILE_NAME}`;
@@ -369849,10 +369800,6 @@ const getEntryPoint = (components, filePath) => {
369849
369800
  exports.getEntryPoint = getEntryPoint;
369850
369801
  const getFilenameFromIFrame = (iframe) => iframe?.querySelector("meta[filename]")?.getAttribute("filename") ?? null;
369851
369802
  exports.getFilenameFromIFrame = getFilenameFromIFrame;
369852
- const isExternalUrl = (url) => {
369853
- return /^(https?:)?\/\//.test(url);
369854
- };
369855
- exports.isExternalUrl = isExternalUrl;
369856
369803
 
369857
369804
 
369858
369805
  /***/ }),
@@ -373451,6 +373398,7 @@ const UserSlice_1 = __webpack_require__(86910);
373451
373398
  const WebComponentProject_1 = __importDefault(__webpack_require__(42086));
373452
373399
  const react_i18next_1 = __webpack_require__(56576);
373453
373400
  const useProject_1 = __webpack_require__(54095);
373401
+ const useProjectStarterCode_1 = __webpack_require__(65529);
373454
373402
  const useProjectPersistence_1 = __webpack_require__(72300);
373455
373403
  const useProjectRemix_1 = __webpack_require__(35602);
373456
373404
  const useProjectTabSync_1 = __webpack_require__(17299);
@@ -373467,7 +373415,7 @@ const stores_1 = __webpack_require__(32132);
373467
373415
  const SaveBeforeLeave_1 = __webpack_require__(47318);
373468
373416
  const selectors_1 = __webpack_require__(43551);
373469
373417
  const WebComponentLoader = (props) => {
373470
- const { locale = "en", projectNameEditable = true, readOnly = false, sidebarOptions = [], project: projectData, saveProject, isSaving, loadProjectContent, projectContent = null, isLoading = false, packageApiUrl, user, isCodeVisible = true, isSharedProject = false, shareLinks, isContentLoaded = null, isSaveSuccess = null, loadTemplateProjectData, isTemplateDataLoaded = false, } = props;
373418
+ const { locale = "en", projectNameEditable = true, readOnly = false, sidebarOptions = [], project: projectData, saveProject, isSaving, loadProjectContent, projectContent = null, starterCodeContent = null, isLoading = false, packageApiUrl, user, isCodeVisible = true, isSharedProject = false, shareLinks, isContentLoaded = null, isSaveSuccess = null, loadTemplateProjectData, isTemplateDataLoaded = false, } = props;
373471
373419
  const dispatch = (0, react_redux_1.useDispatch)();
373472
373420
  const { t } = (0, react_i18next_1.useTranslation)();
373473
373421
  const [searchParams] = (0, react_router_dom_1.useSearchParams)();
@@ -373528,10 +373476,16 @@ const WebComponentLoader = (props) => {
373528
373476
  isLoading,
373529
373477
  isContentLoaded,
373530
373478
  projectContent,
373479
+ starterCodeContent,
373531
373480
  loadProjectContent,
373532
373481
  loadTemplateProjectData,
373533
373482
  isTemplateDataLoaded,
373534
373483
  });
373484
+ (0, useProjectStarterCode_1.useProjectStarterCode)({
373485
+ projectData,
373486
+ starterCodeContent,
373487
+ isLoading,
373488
+ });
373535
373489
  (0, useProjectRemix_1.useProjectRemix)({
373536
373490
  projectData,
373537
373491
  saveProject,
@@ -374225,10 +374179,10 @@ const getUniqueNameFromItem = (childrenNames, baseName, extension = "") => {
374225
374179
  // if there is a conflict, use canonical lowercase for the duplicate
374226
374180
  const normalizedBaseName = baseName.toLowerCase();
374227
374181
  let counter = 1;
374228
- let candidate = `${normalizedBaseName} (${counter})`;
374182
+ let candidate = `${normalizedBaseName}(${counter})`;
374229
374183
  while (existingNamesLower.has(fullName(candidate).toLowerCase())) {
374230
374184
  counter++;
374231
- candidate = `${normalizedBaseName} (${counter})`;
374185
+ candidate = `${normalizedBaseName}(${counter})`;
374232
374186
  }
374233
374187
  return candidate;
374234
374188
  };
@@ -374382,7 +374336,7 @@ const sendToast_1 = __webpack_require__(50068);
374382
374336
  const useUnsavedDraftResolution_1 = __webpack_require__(61171);
374383
374337
  const projectSnapshotHelpers_1 = __webpack_require__(22390);
374384
374338
  const isFinalLoadingState = (state) => state === types_1.LoadingState.SUCCESS || state === types_1.LoadingState.FAILED;
374385
- const useProject = ({ projectData, projectContent = null, isContentLoaded = null, isLoading = false, loadProjectContent, loadTemplateProjectData, isTemplateDataLoaded, }) => {
374339
+ const useProject = ({ projectData, projectContent = null, starterCodeContent = null, isContentLoaded = null, isLoading = false, loadProjectContent, loadTemplateProjectData, isTemplateDataLoaded, }) => {
374386
374340
  const project = (0, stores_1.useAppSelector)((state) => state.editor.project);
374387
374341
  const loadingState = (0, stores_1.useAppSelector)((state) => state.editor.loading);
374388
374342
  const savingState = (0, stores_1.useAppSelector)((state) => state.editor.saving);
@@ -374504,6 +374458,10 @@ const useProject = ({ projectData, projectContent = null, isContentLoaded = null
374504
374458
  }
374505
374459
  // if no specific commitId provided and not preview, set default data
374506
374460
  if (!projectData?.commitId) {
374461
+ // prefer the starter-code archive when one is provided
374462
+ if (starterCodeContent) {
374463
+ return;
374464
+ }
374507
374465
  if (!loadTemplateProjectData || isTemplateDataLoaded) {
374508
374466
  setProjectDataFromTemplate(projectData);
374509
374467
  }
@@ -374537,6 +374495,7 @@ const useProject = ({ projectData, projectContent = null, isContentLoaded = null
374537
374495
  project.commitId,
374538
374496
  saveTriggered,
374539
374497
  savingState,
374498
+ starterCodeContent,
374540
374499
  ]);
374541
374500
  // Load commit data when commitIdLoadTriggered is set
374542
374501
  (0, react_1.useEffect)(() => {
@@ -374999,6 +374958,119 @@ const useProjectRemix = ({ projectData, saveProject, }) => {
374999
374958
  exports.useProjectRemix = useProjectRemix;
375000
374959
 
375001
374960
 
374961
+ /***/ }),
374962
+
374963
+ /***/ 65529:
374964
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
374965
+
374966
+
374967
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
374968
+ exports.useProjectStarterCode = void 0;
374969
+ const react_1 = __webpack_require__(51649);
374970
+ const react_redux_1 = __webpack_require__(14062);
374971
+ const EditorSlice_1 = __webpack_require__(68512);
374972
+ const stores_1 = __webpack_require__(32132);
374973
+ const loadProjectFromZip_1 = __webpack_require__(65404);
374974
+ const types_1 = __webpack_require__(92932);
374975
+ const useIsMounted_1 = __webpack_require__(76314);
374976
+ const useProjectCache_1 = __webpack_require__(69557);
374977
+ const useUnsavedDraftResolution_1 = __webpack_require__(61171);
374978
+ const projectSnapshotHelpers_1 = __webpack_require__(22390);
374979
+ const isFinalLoadingState = (state) => state === types_1.LoadingState.SUCCESS || state === types_1.LoadingState.FAILED;
374980
+ // unpacks a starter-code zip archive and applies it as the initial project
374981
+ const useProjectStarterCode = ({ projectData, starterCodeContent = null, isLoading = false, }) => {
374982
+ const dispatch = (0, react_redux_1.useDispatch)();
374983
+ const isMounted = (0, useIsMounted_1.useIsMounted)();
374984
+ const callIdRef = (0, react_1.useRef)(null);
374985
+ const loadingState = (0, stores_1.useAppSelector)((state) => state.editor.loading);
374986
+ const savingState = (0, stores_1.useAppSelector)((state) => state.editor.saving);
374987
+ const isPreview = (0, stores_1.useAppSelector)((state) => state.editor.isOutputOnly);
374988
+ const commitIdLoadTriggered = (0, stores_1.useAppSelector)((state) => state.editor.commitIdLoadTriggered);
374989
+ const saveTriggered = (0, stores_1.useAppSelector)((state) => state.editor.saveTriggered);
374990
+ const projectFromStore = (0, stores_1.useAppSelector)((state) => state.editor.project);
374991
+ const cacheKey = projectFromStore.identifier ?? projectData?.identifier;
374992
+ const { value: cachedProject, isLoading: isLoadingCache, isLoaded: isCacheLoaded, } = (0, useProjectCache_1.useProjectCache)(cacheKey);
374993
+ // eslint-disable-next-line eqeqeq
374994
+ const isCurrentOutdated = cachedProject?.commitId != projectData?.commitId;
374995
+ const { shouldBypassCachedDraft } = (0, useUnsavedDraftResolution_1.useUnsavedDraftResolution)({
374996
+ projectData,
374997
+ cachedProject,
374998
+ isCurrentOutdated,
374999
+ });
375000
+ (0, react_1.useEffect)(() => {
375001
+ if (!starterCodeContent || !projectData)
375002
+ return;
375003
+ if (isLoading || isLoadingCache)
375004
+ return;
375005
+ if (!isCacheLoaded && cacheKey)
375006
+ return;
375007
+ if (commitIdLoadTriggered)
375008
+ return;
375009
+ if (projectData.commitId)
375010
+ return;
375011
+ if (cachedProject)
375012
+ return;
375013
+ if (isPreview)
375014
+ return;
375015
+ if (saveTriggered || savingState === types_1.SavingState.PROCESS)
375016
+ return;
375017
+ if (isFinalLoadingState(loadingState) && !shouldBypassCachedDraft)
375018
+ return;
375019
+ const callId = Symbol("loadStarterCodeFromZip");
375020
+ callIdRef.current = callId;
375021
+ (0, loadProjectFromZip_1.loadProjectFromZip)({
375022
+ zipBlob: starterCodeContent,
375023
+ identifier: projectData.identifier,
375024
+ })
375025
+ .then((loadedProject) => {
375026
+ if (!isMounted.current)
375027
+ return;
375028
+ if (callIdRef.current !== callId)
375029
+ return;
375030
+ const { commits, ...rest } = projectData;
375031
+ const projectToSet = {
375032
+ ...rest,
375033
+ commitId: null,
375034
+ components: loadedProject.components,
375035
+ rootDirId: loadedProject.rootDirId,
375036
+ };
375037
+ dispatch((0, EditorSlice_1.setProject)({
375038
+ ...projectToSet,
375039
+ lastSavedSnapshot: (0, projectSnapshotHelpers_1.buildProjectSnapshot)(projectToSet),
375040
+ }));
375041
+ dispatch((0, EditorSlice_1.setCommits)({ commits: commits ?? [] }));
375042
+ })
375043
+ .catch((error) => {
375044
+ if (!isMounted.current)
375045
+ return;
375046
+ if (callIdRef.current !== callId)
375047
+ return;
375048
+ console.error("Error loading starter code from zip:", error);
375049
+ dispatch((0, EditorSlice_1.setLoading)(types_1.LoadingState.FAILED));
375050
+ });
375051
+ // trigger only on data/state changes that affect eligibility
375052
+ // eslint-disable-next-line react-hooks/exhaustive-deps
375053
+ }, [
375054
+ starterCodeContent,
375055
+ projectData,
375056
+ isLoading,
375057
+ isLoadingCache,
375058
+ isCacheLoaded,
375059
+ cacheKey,
375060
+ cachedProject,
375061
+ commitIdLoadTriggered,
375062
+ isPreview,
375063
+ loadingState,
375064
+ savingState,
375065
+ saveTriggered,
375066
+ shouldBypassCachedDraft,
375067
+ dispatch,
375068
+ isMounted,
375069
+ ]);
375070
+ };
375071
+ exports.useProjectStarterCode = useProjectStarterCode;
375072
+
375073
+
375002
375074
  /***/ }),
375003
375075
 
375004
375076
  /***/ 17299:
@@ -375091,6 +375163,7 @@ exports.useUnsavedDraftResolution = void 0;
375091
375163
  const react_1 = __webpack_require__(51649);
375092
375164
  const react_redux_1 = __webpack_require__(14062);
375093
375165
  const EditorSlice_1 = __webpack_require__(68512);
375166
+ const stores_1 = __webpack_require__(32132);
375094
375167
  const types_1 = __webpack_require__(92932);
375095
375168
  const projectSnapshotHelpers_1 = __webpack_require__(22390);
375096
375169
  // check if incoming and cached projects are the same draft
@@ -375127,14 +375200,14 @@ const hasCachedDraftChanges = (cachedProject, incomingProject) => {
375127
375200
  const useUnsavedDraftResolution = ({ projectData, cachedProject, isCurrentOutdated, }) => {
375128
375201
  const dispatch = (0, react_redux_1.useDispatch)();
375129
375202
  // flag to ignore cached draft after Start New
375130
- const [shouldBypassCachedDraft, setShouldBypassCachedDraft] = (0, react_1.useState)(false);
375203
+ const shouldBypassCachedDraft = (0, stores_1.useAppSelector)((state) => state.editor.shouldBypassCachedDraft);
375131
375204
  const isSameDraftType = (0, react_1.useMemo)(() => isSameDraftProjectType(projectData, cachedProject), [projectData, cachedProject]);
375132
375205
  const hasDraftChanges = (0, react_1.useMemo)(() => hasCachedDraftChanges(cachedProject, projectData), [cachedProject, projectData]);
375133
375206
  (0, react_1.useEffect)(() => {
375134
375207
  if (projectData?.commitId && shouldBypassCachedDraft) {
375135
- setShouldBypassCachedDraft(false);
375208
+ dispatch((0, EditorSlice_1.setShouldBypassCachedDraft)(false));
375136
375209
  }
375137
- }, [projectData?.commitId, shouldBypassCachedDraft]);
375210
+ }, [projectData?.commitId, shouldBypassCachedDraft, dispatch]);
375138
375211
  // show modal only for matching draft when cache is valid and not bypassed
375139
375212
  const shouldShowUnsavedDraftModal = !!cachedProject &&
375140
375213
  !isCurrentOutdated &&
@@ -375146,8 +375219,8 @@ const useUnsavedDraftResolution = ({ projectData, cachedProject, isCurrentOutdat
375146
375219
  modal: types_1.ModalType.DRAFT_UNSAVED_CHANGES,
375147
375220
  args: {
375148
375221
  type: types_1.ModalType.DRAFT_UNSAVED_CHANGES,
375149
- onContinue: () => setShouldBypassCachedDraft(false),
375150
- onStartNew: () => setShouldBypassCachedDraft(true),
375222
+ onContinue: () => dispatch((0, EditorSlice_1.setShouldBypassCachedDraft)(false)),
375223
+ onStartNew: () => dispatch((0, EditorSlice_1.setShouldBypassCachedDraft)(true)),
375151
375224
  },
375152
375225
  }));
375153
375226
  }, [dispatch]);
@@ -375169,7 +375242,7 @@ exports.useUnsavedDraftResolution = useUnsavedDraftResolution;
375169
375242
  var _a;
375170
375243
  Object.defineProperty(exports, "__esModule", ({ value: true }));
375171
375244
  exports.setRemixTriggered = exports.setIsLoadingCommit = exports.setCommitIdLoadTriggered = exports.setShareLinks = exports.setCommits = exports.setSharedStatus = exports.setCodeVisibility = exports.applyComponentsPatch = exports.setSaving = exports.disableTheming = exports.hideSidebar = exports.showSidebar = exports.closeModal = exports.showModal = exports.closeErrorModal = exports.showErrorModal = exports.updateProjectName = exports.setProjectNameDraft = exports.setSaveTriggered = exports.triggerDraw = exports.triggerCodeRun = exports.stopDraw = exports.stopCodeRun = exports.setLoading = exports.setReadOnly = exports.updateProjectIdentifier = exports.updateProjectCommits = exports.updateProjectSnapshot = exports.setProject = exports.setHasShownSavePrompt = exports.setError = exports.setCascadeUpdate = exports.setIsOutputOnly = exports.setAutorunEnabled = exports.setPage = exports.setFocussedFileIndex = exports.addFilePanel = exports.setOpenedFiles = exports.openFile = exports.closeFile = exports.expireJustLoaded = exports.codeRunHandled = exports.resetRunner = exports.setLoadedRunner = exports.loadingRunner = exports.updateProjectComponent = exports.addProjectComponent = exports.resetState = exports.EditorSlice = exports.editorInitialState = void 0;
375172
- exports.queueBinaryWrites = exports.revertProject = void 0;
375245
+ exports.queueBinaryWrites = exports.revertProject = exports.setShouldBypassCachedDraft = void 0;
375173
375246
  const toolkit_1 = __webpack_require__(12069);
375174
375247
  const ProjectTypes_1 = __webpack_require__(27130);
375175
375248
  const types_1 = __webpack_require__(92932);
@@ -375218,6 +375291,7 @@ exports.editorInitialState = {
375218
375291
  shareLinks: null,
375219
375292
  remixTriggered: false,
375220
375293
  projectNameDraft: null,
375294
+ shouldBypassCachedDraft: false,
375221
375295
  };
375222
375296
  exports.EditorSlice = (0, toolkit_1.createSlice)({
375223
375297
  name: "editor",
@@ -375500,10 +375574,13 @@ exports.EditorSlice = (0, toolkit_1.createSlice)({
375500
375574
  setRemixTriggered: (state, action) => {
375501
375575
  state.remixTriggered = action.payload;
375502
375576
  },
375577
+ setShouldBypassCachedDraft: (state, action) => {
375578
+ state.shouldBypassCachedDraft = action.payload;
375579
+ },
375503
375580
  },
375504
375581
  });
375505
375582
  // Action creators are generated for each case reducer function
375506
- _a = exports.EditorSlice.actions, exports.resetState = _a.resetState, exports.addProjectComponent = _a.addProjectComponent, exports.updateProjectComponent = _a.updateProjectComponent, exports.loadingRunner = _a.loadingRunner, exports.setLoadedRunner = _a.setLoadedRunner, exports.resetRunner = _a.resetRunner, exports.codeRunHandled = _a.codeRunHandled, exports.expireJustLoaded = _a.expireJustLoaded, exports.closeFile = _a.closeFile, exports.openFile = _a.openFile, exports.setOpenedFiles = _a.setOpenedFiles, exports.addFilePanel = _a.addFilePanel, exports.setFocussedFileIndex = _a.setFocussedFileIndex, exports.setPage = _a.setPage, exports.setAutorunEnabled = _a.setAutorunEnabled, exports.setIsOutputOnly = _a.setIsOutputOnly, exports.setCascadeUpdate = _a.setCascadeUpdate, exports.setError = _a.setError, exports.setHasShownSavePrompt = _a.setHasShownSavePrompt, exports.setProject = _a.setProject, exports.updateProjectSnapshot = _a.updateProjectSnapshot, exports.updateProjectCommits = _a.updateProjectCommits, exports.updateProjectIdentifier = _a.updateProjectIdentifier, exports.setReadOnly = _a.setReadOnly, exports.setLoading = _a.setLoading, exports.stopCodeRun = _a.stopCodeRun, exports.stopDraw = _a.stopDraw, exports.triggerCodeRun = _a.triggerCodeRun, exports.triggerDraw = _a.triggerDraw, exports.setSaveTriggered = _a.setSaveTriggered, exports.setProjectNameDraft = _a.setProjectNameDraft, exports.updateProjectName = _a.updateProjectName, exports.showErrorModal = _a.showErrorModal, exports.closeErrorModal = _a.closeErrorModal, exports.showModal = _a.showModal, exports.closeModal = _a.closeModal, exports.showSidebar = _a.showSidebar, exports.hideSidebar = _a.hideSidebar, exports.disableTheming = _a.disableTheming, exports.setSaving = _a.setSaving, exports.applyComponentsPatch = _a.applyComponentsPatch, exports.setCodeVisibility = _a.setCodeVisibility, exports.setSharedStatus = _a.setSharedStatus, exports.setCommits = _a.setCommits, exports.setShareLinks = _a.setShareLinks, exports.setCommitIdLoadTriggered = _a.setCommitIdLoadTriggered, exports.setIsLoadingCommit = _a.setIsLoadingCommit, exports.setRemixTriggered = _a.setRemixTriggered, exports.revertProject = _a.revertProject;
375583
+ _a = exports.EditorSlice.actions, exports.resetState = _a.resetState, exports.addProjectComponent = _a.addProjectComponent, exports.updateProjectComponent = _a.updateProjectComponent, exports.loadingRunner = _a.loadingRunner, exports.setLoadedRunner = _a.setLoadedRunner, exports.resetRunner = _a.resetRunner, exports.codeRunHandled = _a.codeRunHandled, exports.expireJustLoaded = _a.expireJustLoaded, exports.closeFile = _a.closeFile, exports.openFile = _a.openFile, exports.setOpenedFiles = _a.setOpenedFiles, exports.addFilePanel = _a.addFilePanel, exports.setFocussedFileIndex = _a.setFocussedFileIndex, exports.setPage = _a.setPage, exports.setAutorunEnabled = _a.setAutorunEnabled, exports.setIsOutputOnly = _a.setIsOutputOnly, exports.setCascadeUpdate = _a.setCascadeUpdate, exports.setError = _a.setError, exports.setHasShownSavePrompt = _a.setHasShownSavePrompt, exports.setProject = _a.setProject, exports.updateProjectSnapshot = _a.updateProjectSnapshot, exports.updateProjectCommits = _a.updateProjectCommits, exports.updateProjectIdentifier = _a.updateProjectIdentifier, exports.setReadOnly = _a.setReadOnly, exports.setLoading = _a.setLoading, exports.stopCodeRun = _a.stopCodeRun, exports.stopDraw = _a.stopDraw, exports.triggerCodeRun = _a.triggerCodeRun, exports.triggerDraw = _a.triggerDraw, exports.setSaveTriggered = _a.setSaveTriggered, exports.setProjectNameDraft = _a.setProjectNameDraft, exports.updateProjectName = _a.updateProjectName, exports.showErrorModal = _a.showErrorModal, exports.closeErrorModal = _a.closeErrorModal, exports.showModal = _a.showModal, exports.closeModal = _a.closeModal, exports.showSidebar = _a.showSidebar, exports.hideSidebar = _a.hideSidebar, exports.disableTheming = _a.disableTheming, exports.setSaving = _a.setSaving, exports.applyComponentsPatch = _a.applyComponentsPatch, exports.setCodeVisibility = _a.setCodeVisibility, exports.setSharedStatus = _a.setSharedStatus, exports.setCommits = _a.setCommits, exports.setShareLinks = _a.setShareLinks, exports.setCommitIdLoadTriggered = _a.setCommitIdLoadTriggered, exports.setIsLoadingCommit = _a.setIsLoadingCommit, exports.setRemixTriggered = _a.setRemixTriggered, exports.setShouldBypassCachedDraft = _a.setShouldBypassCachedDraft, exports.revertProject = _a.revertProject;
375507
375584
  exports.queueBinaryWrites = (0, toolkit_1.createAction)("editor/queueBinaryWrites");
375508
375585
  exports["default"] = exports.EditorSlice.reducer;
375509
375586
 
@@ -375643,7 +375720,6 @@ const utils_1 = __webpack_require__(33321);
375643
375720
  const sendToast_1 = __webpack_require__(50068);
375644
375721
  const constants_1 = __webpack_require__(16287);
375645
375722
  const fileValidation_1 = __webpack_require__(59615);
375646
- const macSystemFiles_1 = __webpack_require__(18213);
375647
375723
  const selectComponents = (state) => {
375648
375724
  return state.editor.project.components ?? [];
375649
375725
  };
@@ -375715,27 +375791,23 @@ exports.fsRemove = fsRemove;
375715
375791
  const fsImportFiles = ({ files, parentId }) => async (dispatch, getState) => {
375716
375792
  const state = getState();
375717
375793
  const components = selectComponents(state);
375718
- // Ignore macOS system files (__MACOSX/, .DS_Store)
375719
- const importableFiles = (0, macSystemFiles_1.filterOutMacSystemFiles)(files);
375720
- if (importableFiles.length === 0)
375721
- return;
375722
- if (!(0, utils_1.validateProjectFilesCount)(components, importableFiles.length)) {
375794
+ if (!(0, utils_1.validateProjectFilesCount)(components, files.length)) {
375723
375795
  (0, sendToast_1.showError)(filesCountErrorMessage);
375724
375796
  return;
375725
375797
  }
375726
- const { validFiles, importErrors } = (0, fileValidation_1.validateFiles)(importableFiles);
375798
+ const { validFiles, importErrors } = (0, fileValidation_1.validateFiles)(files);
375727
375799
  const hasDuplicates = hasCaseInsensitiveDuplicateNames(validFiles);
375728
375800
  for (const error of importErrors) {
375729
375801
  (0, sendToast_1.showError)(error.reason);
375730
375802
  }
375731
375803
  if (validFiles.length > 0) {
375732
- (0, sendToast_1.showSuccess)(`Selected files uploaded successfully. ${validFiles.length} of ${importableFiles.length} file(s) uploaded.`);
375804
+ (0, sendToast_1.showSuccess)(`Selected files uploaded successfully. ${validFiles.length} of ${files.length} file(s) uploaded.`);
375733
375805
  if (hasDuplicates) {
375734
375806
  (0, sendToast_1.showInfo)(DUPLICATE_UPLOAD_TOAST);
375735
375807
  }
375736
375808
  }
375737
375809
  else {
375738
- (0, sendToast_1.showError)(`Selected files couldn’t be uploaded. ${importableFiles.length} file(s) failed to upload.`);
375810
+ (0, sendToast_1.showError)(`Selected files couldn’t be uploaded. ${files.length} file(s) failed to upload.`);
375739
375811
  return;
375740
375812
  }
375741
375813
  const componentsMap = (0, utils_1.buildComponentsById)(components);
@@ -375752,28 +375824,24 @@ const fsImportFolder = ({ files, parentId }) => async (dispatch, getState) => {
375752
375824
  return;
375753
375825
  const state = getState();
375754
375826
  const components = selectComponents(state);
375755
- // Ignore macOS system files (__MACOSX/, .DS_Store)
375756
- const importableFiles = (0, macSystemFiles_1.filterOutMacSystemFiles)(files);
375757
- if (importableFiles.length === 0)
375758
- return;
375759
- if (!(0, utils_1.validateProjectFilesCount)(components, importableFiles.length)) {
375827
+ if (!(0, utils_1.validateProjectFilesCount)(components, files.length)) {
375760
375828
  (0, sendToast_1.showError)(filesCountErrorMessage);
375761
375829
  return;
375762
375830
  }
375763
- const folderName = importableFiles[0]?.webkitRelativePath?.split("/")[0];
375764
- const { validFiles, importErrors } = (0, fileValidation_1.validateFiles)(importableFiles);
375831
+ const folderName = files[0]?.webkitRelativePath?.split("/")[0];
375832
+ const { validFiles, importErrors } = (0, fileValidation_1.validateFiles)(files);
375765
375833
  const hasDuplicates = hasCaseInsensitiveDuplicateNames(validFiles);
375766
375834
  for (const error of importErrors) {
375767
375835
  (0, sendToast_1.showError)(error.reason);
375768
375836
  }
375769
375837
  if (validFiles.length > 0) {
375770
- (0, sendToast_1.showSuccess)(`Folder '${folderName}' upload complete. ${validFiles.length} of ${importableFiles.length} file(s) uploaded successfully.`);
375838
+ (0, sendToast_1.showSuccess)(`Folder '${folderName}' upload complete. ${validFiles.length} of ${files.length} file(s) uploaded successfully.`);
375771
375839
  if (hasDuplicates) {
375772
375840
  (0, sendToast_1.showInfo)(DUPLICATE_UPLOAD_TOAST);
375773
375841
  }
375774
375842
  }
375775
375843
  else {
375776
- (0, sendToast_1.showError)(`'${folderName}' folder couldn't be uploaded. ${importableFiles.length} file(s) failed to upload.`);
375844
+ (0, sendToast_1.showError)(`'${folderName}' folder couldn't be uploaded. ${files.length} file(s) failed to upload.`);
375777
375845
  return;
375778
375846
  }
375779
375847
  const componentsMap = (0, utils_1.buildComponentsById)(components);
@@ -376642,6 +376710,57 @@ const downloadProjectFile = async (file, fileName) => {
376642
376710
  exports.downloadProjectFile = downloadProjectFile;
376643
376711
 
376644
376712
 
376713
+ /***/ }),
376714
+
376715
+ /***/ 31615:
376716
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
376717
+
376718
+
376719
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
376720
+ exports.matchingRegexes = exports.allowedInternalLinks = exports.allowedExternalLinks = exports.useExternalLinkState = void 0;
376721
+ const react_1 = __webpack_require__(51649);
376722
+ const react_redux_1 = __webpack_require__(14062);
376723
+ const EditorSlice_1 = __webpack_require__(68512);
376724
+ const Errors_1 = __webpack_require__(20339);
376725
+ const domain = "https://rpf.io/";
376726
+ const host = window?.location?.origin || "http://localhost:3011";
376727
+ const rpfDomain = new RegExp(`^${domain}`);
376728
+ const hostDomain = new RegExp(`^${host}`);
376729
+ const allowedInternalLinks = [new RegExp(`^#[a-zA-Z0-9]+`)];
376730
+ exports.allowedInternalLinks = allowedInternalLinks;
376731
+ const allowedExternalLinks = [rpfDomain, hostDomain];
376732
+ exports.allowedExternalLinks = allowedExternalLinks;
376733
+ const useExternalLinkState = (showModal) => {
376734
+ const dispatch = (0, react_redux_1.useDispatch)();
376735
+ const [externalLink, setExternalLink] = (0, react_1.useState)(null);
376736
+ const handleAllowedExternalLink = (linkTo) => {
376737
+ setExternalLink(linkTo);
376738
+ dispatch((0, EditorSlice_1.triggerCodeRun)());
376739
+ };
376740
+ const handleRegularExternalLink = (linkTo) => {
376741
+ setExternalLink(null);
376742
+ dispatch((0, EditorSlice_1.setPage)(linkTo));
376743
+ dispatch((0, EditorSlice_1.triggerCodeRun)());
376744
+ };
376745
+ const handleExternalLinkError = () => {
376746
+ dispatch((0, EditorSlice_1.setError)({ type: Errors_1.ErrorType.EXTERNAL_LINK }));
376747
+ showModal();
376748
+ };
376749
+ return {
376750
+ externalLink,
376751
+ setExternalLink,
376752
+ handleAllowedExternalLink,
376753
+ handleRegularExternalLink,
376754
+ handleExternalLinkError,
376755
+ };
376756
+ };
376757
+ exports.useExternalLinkState = useExternalLinkState;
376758
+ const matchingRegexes = (regexArray, testString) => {
376759
+ return regexArray.some((reg) => reg.test(testString));
376760
+ };
376761
+ exports.matchingRegexes = matchingRegexes;
376762
+
376763
+
376645
376764
  /***/ }),
376646
376765
 
376647
376766
  /***/ 65404:
@@ -376659,7 +376778,6 @@ const ProjectTypes_1 = __webpack_require__(27130);
376659
376778
  const projectHelpers_1 = __webpack_require__(2610);
376660
376779
  const binaryStore_1 = __webpack_require__(5060);
376661
376780
  const projectBinaryIndex_1 = __webpack_require__(3044);
376662
- const macSystemFiles_1 = __webpack_require__(18213);
376663
376781
  function normPath(p) {
376664
376782
  let s = (p || "").replace(/\\/g, "/").replace(/^\/+/, "");
376665
376783
  // remove extra folder in zipball zip
@@ -376686,7 +376804,7 @@ function pushChild(parent, childId) {
376686
376804
  parent.children.push(childId);
376687
376805
  }
376688
376806
  function getCommonRootFolder(entries) {
376689
- const fileEntries = entries.filter((entry) => !entry.dir && !(0, macSystemFiles_1.isMacSystemFilePath)(entry.name));
376807
+ const fileEntries = entries.filter((entry) => !entry.dir);
376690
376808
  const [firstFileEntry] = fileEntries;
376691
376809
  if (!firstFileEntry)
376692
376810
  return "";
@@ -376721,13 +376839,10 @@ async function loadProjectFromZip(opts) {
376721
376839
  for (const entry of entries) {
376722
376840
  if (entry.dir)
376723
376841
  continue;
376724
- // skip mac system files (__MACOSX/, .DS_Store)
376725
- if ((0, macSystemFiles_1.isMacSystemFilePath)(entry.name))
376726
- continue;
376727
- const normalized = normPath(entry.name);
376728
- const raw = rootPath && normalized.startsWith(rootPath)
376729
- ? normalized.slice(rootPath.length)
376730
- : normalized;
376842
+ const normalizedEntryName = normPath(entry.name);
376843
+ const raw = rootPath
376844
+ ? normPath(normalizedEntryName.slice(rootPath.length))
376845
+ : normalizedEntryName;
376731
376846
  if (!raw)
376732
376847
  continue;
376733
376848
  const parts = raw.split("/").filter(Boolean);
@@ -376787,34 +376902,6 @@ async function loadProjectFromZip(opts) {
376787
376902
  }
376788
376903
 
376789
376904
 
376790
- /***/ }),
376791
-
376792
- /***/ 18213:
376793
- /***/ ((__unused_webpack_module, exports) => {
376794
-
376795
-
376796
- Object.defineProperty(exports, "__esModule", ({ value: true }));
376797
- exports.filterOutMacSystemFiles = exports.isMacSystemFilePath = void 0;
376798
- const normalizeSlashes = (p) => (p || "").replace(/\\/g, "/").trim();
376799
- const isMacSystemFilePath = (rawEntryPath) => {
376800
- const path = normalizeSlashes(rawEntryPath);
376801
- if (!path)
376802
- return false;
376803
- const fileName = path.split("/").filter(Boolean).pop() || "";
376804
- // skip mac system files
376805
- if (path.startsWith("__MACOSX/") || path.includes("/__MACOSX/"))
376806
- return true;
376807
- if (fileName === ".DS_Store")
376808
- return true;
376809
- if (fileName.startsWith("._"))
376810
- return true;
376811
- return false;
376812
- };
376813
- exports.isMacSystemFilePath = isMacSystemFilePath;
376814
- const filterOutMacSystemFiles = (files) => Array.from(files).filter((file) => !(0, exports.isMacSystemFilePath)(file.webkitRelativePath?.trim() || file.name));
376815
- exports.filterOutMacSystemFiles = filterOutMacSystemFiles;
376816
-
376817
-
376818
376905
  /***/ }),
376819
376906
 
376820
376907
  /***/ 3044:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gwchq-textjam",
3
3
  "description": "Embeddable React editor used in Raspberry Pi text-based projects.",
4
- "version": "0.3.1",
4
+ "version": "0.4.0",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/GirlsFirst/gwchq-textjam",
7
7
  "author": "Girls Who Code HQ",
@@ -21,7 +21,6 @@
21
21
  "lint:fix": "eslint --fix \"src/**/*.{js,jsx,json}\"",
22
22
  "stylelint": "stylelint src/**/*.scss",
23
23
  "test": "jest",
24
- "test:coverage": "jest --coverage --json --outputFile=report.json",
25
24
  "prepare": "husky"
26
25
  },
27
26
  "module": "./dist/index.js",
@@ -98,7 +97,7 @@
98
97
  "react-refresh": "^0.8.3",
99
98
  "react-responsive": "^9.0.2",
100
99
  "react-router-dom": "^6.7.0",
101
- "react-tabs": "^4.2.1",
100
+ "react-tabs": "^3.2.3",
102
101
  "react-timer-hook": "^3.0.5",
103
102
  "react-toastify": "^8.1.0",
104
103
  "react-toggle": "^4.1.3",
@@ -123,6 +122,7 @@
123
122
  "@babel/preset-env": "^7.17.10",
124
123
  "@babel/preset-typescript": "^7.28.5",
125
124
  "@pmmmwh/react-refresh-webpack-plugin": "0.4.3",
125
+ "@react-three/test-renderer": "8.2.1",
126
126
  "@svgr/webpack": "5.5.0",
127
127
  "@testing-library/jest-dom": "^5.16.5",
128
128
  "@testing-library/react": "14.3.1",
@@ -165,7 +165,6 @@
165
165
  "file-loader": "6.1.1",
166
166
  "html-webpack-plugin": "5.6.0",
167
167
  "husky": "^9.1.7",
168
- "identity-obj-proxy": "^3.0.0",
169
168
  "jest": "^29.1.2",
170
169
  "jest-circus": "^29.1.2",
171
170
  "jest-css-modules-transform": "^4.4.2",
@@ -274,9 +273,7 @@
274
273
  "^utils/(.*)$": "<rootDir>/src/utils/$1",
275
274
  "^components/(.*)$": "<rootDir>/src/components/$1",
276
275
  "^assets/(.*)$": "<rootDir>/src/assets/$1",
277
- "^store/(.*)$": "<rootDir>/src/redux/$1",
278
- "^domain/(.*)$": "<rootDir>/src/domain/$1",
279
- "^.+\\?url$": "<rootDir>/node_modules/jest-transform-stub"
276
+ "^store/(.*)$": "<rootDir>/src/redux/$1"
280
277
  },
281
278
  "moduleFileExtensions": [
282
279
  "web.js",
@@ -294,8 +291,7 @@
294
291
  "jest-watch-typeahead/filename",
295
292
  "jest-watch-typeahead/testname"
296
293
  ],
297
- "resetMocks": true,
298
- "coverageDirectory": "./coverage"
294
+ "resetMocks": true
299
295
  },
300
296
  "packageManager": "yarn@3.4.1"
301
297
  }