yourjs-box 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -20
- package/dist/yourjs-box.full.js +356 -33
- package/dist/yourjs-box.js +91 -11
- package/dist/yourjs-box.min.js +7 -7
- package/package.json +2 -1
package/dist/yourjs-box.full.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
/*! yourjs-box v1.
|
|
1
|
+
/*! yourjs-box v1.10.0 | (c) 2023-present Chris West | MIT License | https://github.com/westc/yourjs-box */
|
|
2
2
|
(() => {
|
|
3
3
|
/**
|
|
4
4
|
* Viewer IFRAME's CSS code
|
|
5
5
|
* @type {string}
|
|
6
6
|
*/
|
|
7
|
-
const VIEWER_IFRAME_CSS = "[v-cloak]{display:none}body,html{height:100%;margin:0}body{background-color:var(--console-bg);font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}#vueApp{display:flex;flex-direction:column;inset:0;position:fixed}#main{display:grid;flex-grow:1;gap:0;min-height:0;position:relative}#main.is-moving-divider{-webkit-user-select:none;user-select:none}#main.col-orient.is-moving-divider{cursor:col-resize}#main.row-orient.is-moving-divider{cursor:row-resize}#main.is-moving-divider::after{background-color:var(--accent);content:'';position:absolute;z-index:99}#main.col-orient.is-moving-divider::after{bottom:0;left:calc(100% - var(--temp-editor-pct) - var(--divider-size)/ 2 - 1px);top:0;width:2px}#main.row-orient.is-moving-divider::after{height:2px;left:0;right:0;top:calc(100% - var(--temp-editor-pct) - var(--divider-size)/ 2 - 1px)}#main.col-orient{grid-template-columns:1fr var(--divider-size) calc(var(--editor-pct) - var(--divider-size)/ 2);grid-template-rows:1fr}#main.row-orient{grid-template-columns:1fr;grid-template-rows:1fr var(--divider-size) calc(var(--editor-pct) - var(--divider-size)/ 2)}.divider{background-color:var(--divider-bg);box-sizing:border-box;position:relative;transition:background-color .15s}#main.col-orient .divider{border-left:1px solid var(--toolbar-border);border-right:1px solid var(--toolbar-border);cursor:col-resize}#main.row-orient .divider{border-bottom:1px solid var(--toolbar-border);border-top:1px solid var(--toolbar-border);cursor:row-resize}.divider::after{background-color:var(--divider-grip);border-radius:2px;content:'';left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);transition:background-color .15s}#main.col-orient .divider::after{height:32px;width:2px}#main.row-orient .divider::after{height:2px;width:32px}#main.is-moving-divider .divider,.divider:hover{background-color:var(--divider-hover-bg)}#main.is-moving-divider .divider::after,.divider:hover::after{background-color:var(--accent)}#displays{position:relative}#displays>div{position:absolute;inset:0;overflow:auto}#bottomNav{align-items:center;background-color:var(--toolbar-bg);border-top:1px solid var(--toolbar-border);color:var(--toolbar-text);display:flex;flex:0 0 auto;font-size:12px;gap:8px;height:32px;padding:0 6px 0 8px;-webkit-user-select:none;user-select:none}#bottomNav>.brand{align-items:center;display:flex;flex-grow:1;gap:8px}.runner-badge{border:1px solid var(--toolbar-border);border-radius:999px;color:var(--muted-text);font-size:10px;letter-spacing:.04em;line-height:15px;padding:0 6px;text-transform:uppercase}#bottomNav>.buttons{align-items:center;display:flex;gap:2px}#bottomNav .separator{background-color:var(--toolbar-border);height:16px;margin:0 4px;width:1px}#bottomNav button{align-items:center;background:0 0;border:0;border-radius:4px;color:inherit;cursor:pointer;display:inline-flex;font:inherit;gap:5px;height:24px;justify-content:center;min-width:26px;padding:0 6px}#bottomNav button>span:not(.label){display:inline-flex;font-size:14px}#bottomNav button svg:not(.spin){transform:none!important}#bottomNav button:hover:not(:disabled){background-color:var(--button-hover)}#bottomNav button:disabled{cursor:not-allowed;opacity:.45}#bottomNav button.primary{background-color:var(--accent);color:var(--accent-text);font-weight:600;margin-left:2px;padding:0 10px 0 8px}#bottomNav button.primary>span:not(.label){font-size:11px}#bottomNav button.primary:hover:not(:disabled){background-color:var(--accent-hover)}#bottomNav button:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.spin{animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.logo{align-items:center;color:var(--toolbar-text);display:inline-flex;font-weight:700;gap:5px;letter-spacing:-.01em;line-height:1}.logo-mark{align-items:flex-end;background-color:#f7df1e;border-radius:3px;box-sizing:border-box;color:#1a1a1a;display:inline-flex;font-size:9.5px;font-weight:800;height:18px;justify-content:flex-end;letter-spacing:-.02em;padding:0 2px 2px 0;width:18px}.logo-text{font-size:14px}.logo-large{gap:12px}.logo-large>.logo-mark{border-radius:10px;box-shadow:0 8px 24px rgb(0 0 0 / .18);font-size:24px;height:56px;padding:0 6px 5px 0;width:56px}.logo-large>.logo-text{font-size:36px}#splash{align-items:center;background-color:var(--console-bg);display:flex;inset:0;justify-content:center;position:fixed;transition:opacity .35s ease,visibility .35s;z-index:1000}#splash.hidden{opacity:0;visibility:hidden}.splash-content{align-items:center;animation:splash-in .4s ease-out both;display:flex;flex-direction:column;gap:24px}.splash-progress{background-color:var(--toolbar-border);border-radius:3px;height:3px;overflow:hidden;width:140px}.splash-progress>div{animation:splash-progress 1.1s ease-in-out infinite;background-color:var(--accent);border-radius:inherit;height:100%;width:40%}@keyframes splash-in{from{opacity:0;transform:translateY(6px)}}@keyframes splash-progress{from{transform:translateX(-100%)}to{transform:translateX(250%)}}.dialog-backdrop{align-items:center;animation:fade-in .12s ease-out;background-color:var(--backdrop);display:flex;inset:0;justify-content:center;position:fixed;z-index:900}.dialog{animation:dialog-in .15s ease-out;background-color:var(--dialog-bg);border:1px solid var(--toolbar-border);border-radius:8px;box-shadow:0 12px 40px rgb(0 0 0 / .3);color:var(--console-text);font-size:13px;padding:16px;width:min(360px,calc(100% - 32px))}.dialog-title{font-size:14px;font-weight:600;margin-bottom:6px}.dialog-message{color:var(--muted-text);line-height:1.45}.dialog-buttons{display:flex;gap:8px;justify-content:flex-end;margin-top:16px}.dialog-button{background:0 0;border:1px solid var(--toolbar-border);border-radius:4px;color:inherit;cursor:pointer;font:inherit;height:28px;padding:0 12px}.dialog-button:hover{background-color:var(--button-hover)}.dialog-button.primary{background-color:var(--accent);border-color:var(--accent);color:var(--accent-text);font-weight:600}.dialog-button.primary:hover{background-color:var(--accent-hover)}.dialog-button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}#bottomNav button.logo-button{margin-left:-4px;padding:0 4px}.about-dialog{display:flex;flex-direction:column;max-height:calc(100% - 24px);padding:0;width:min(620px,calc(100% - 24px))}.about-dialog.is-export{height:min(560px,calc(100% - 24px))}.about-header{align-items:center;border-bottom:1px solid var(--toolbar-border);display:flex;flex:0 0 auto;padding:0 8px 0 12px}.tabs{display:flex;flex-grow:1;gap:4px}.tab{background:0 0;border:0;border-bottom:2px solid transparent;color:var(--muted-text);cursor:pointer;font:inherit;font-weight:600;padding:10px 8px 8px}.tab:hover{color:var(--console-text)}.tab.active{border-bottom-color:var(--accent);color:var(--console-text)}.close-button{align-items:center;background:0 0;border:0;border-radius:4px;color:var(--muted-text);cursor:pointer;display:inline-flex;font-size:16px;height:28px;justify-content:center;width:28px}.close-button:hover{background-color:var(--button-hover);color:var(--console-text)}.close-button:focus-visible,.segmented>button:focus-visible,.tab:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}.about-body{flex:1 1 auto;min-height:0;overflow:auto;padding:16px}.about-title{align-items:center;display:flex;gap:12px}.logo-medium>.logo-mark{border-radius:7px;font-size:16px;height:40px;padding:0 4px 4px 0;width:40px}.logo-medium>.logo-text{display:none}.about-name{font-size:17px;font-weight:700}.about-aka{color:var(--muted-text);font-size:13px;font-weight:400;margin-left:4px}.about-version{color:var(--muted-text);font-size:12px}.about-description{line-height:1.5;margin:12px 0}.about-links{display:flex;flex-wrap:wrap;gap:6px 16px}.about-links a{color:var(--accent);font-weight:600;text-decoration:none}.about-links a:hover{text-decoration:underline}.about-body h3{color:var(--muted-text);font-size:11px;letter-spacing:.05em;margin:18px 0 6px;text-transform:uppercase}.about-details{display:grid;gap:4px 16px;grid-template-columns:max-content 1fr;margin:0}.about-details dt{color:var(--muted-text)}.about-details dd{margin:0}.about-details kbd{background-color:var(--toolbar-bg);border:1px solid var(--toolbar-border);border-bottom-width:2px;border-radius:4px;font-family:inherit;font-size:11px;padding:0 4px}.about-footer{border-top:1px solid var(--toolbar-border);color:var(--muted-text);font-size:12px;margin-top:18px;padding-top:12px}.export-body{display:flex;flex-direction:column;gap:10px}.export-options{display:flex;flex-wrap:wrap;gap:8px;justify-content:space-between}.segmented{border:1px solid var(--toolbar-border);border-radius:6px;display:inline-flex;overflow:hidden}.segmented>button{background:0 0;border:0;color:var(--muted-text);cursor:pointer;font:inherit;font-size:12px;font-weight:600;padding:5px 10px}.segmented>button+button{border-left:1px solid var(--toolbar-border)}.segmented>button:hover{background-color:var(--button-hover)}.segmented>button.active{background-color:var(--accent);color:var(--accent-text)}.export-note{color:var(--muted-text);font-size:12px}.export-preview{border:1px solid var(--toolbar-border);border-radius:6px;flex:1 1 auto;min-height:120px;overflow:hidden}.export-actions{display:flex;gap:8px;justify-content:flex-end}.file-input{display:none}.more-menu{background-color:var(--dialog-bg);border:1px solid var(--toolbar-border);border-radius:8px;bottom:38px;box-shadow:0 8px 28px rgb(0 0 0 / .25);color:var(--console-text);font-size:13px;max-height:calc(100% - 48px);min-width:236px;overflow:auto;padding:4px;position:absolute;right:6px;z-index:800}.menu-item{align-items:center;background:0 0;border:0;border-radius:5px;color:inherit;cursor:pointer;display:flex;font:inherit;gap:8px;padding:6px 8px;text-align:left;width:100%}.menu-item:focus-visible,.menu-item:hover:not(:disabled){background-color:var(--button-hover);outline:0}.menu-item:disabled{cursor:not-allowed;opacity:.4}.menu-icon{color:var(--muted-text);display:inline-flex;font-size:14px;justify-content:center;width:16px}.menu-icon svg{transform:none!important}.menu-row{align-items:center;display:flex;gap:2px;padding:2px 2px 2px 8px}.menu-row-label{flex-grow:1}.menu-step{font-weight:600;justify-content:center;width:auto}.menu-percent{color:var(--muted-text);font-variant-numeric:tabular-nums;font-weight:400;min-width:52px}.menu-separator{background-color:var(--toolbar-border);height:1px;margin:4px 0}.history-panel{max-width:min(420px,calc(100% - 12px));width:360px}.history-title{color:var(--muted-text);font-size:11px;font-weight:600;letter-spacing:.05em;padding:4px 8px 6px;text-transform:uppercase}.history-item{justify-content:space-between}.history-label{font-family:var(--console-font);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.history-detail{color:var(--muted-text);flex-shrink:0;font-size:11px}.history-empty,.history-hint{color:var(--muted-text);font-size:12px;padding:6px 8px}.history-hint{border-top:1px solid var(--toolbar-border);margin-top:4px}.value-menu{bottom:auto;min-width:220px;position:fixed;right:auto;z-index:850}.toast{animation:fade-in .15s ease-out;background-color:var(--console-text);border-radius:6px;bottom:44px;box-shadow:0 6px 20px rgb(0 0 0 / .25);color:var(--console-bg);font-size:12px;left:50%;max-width:calc(100% - 32px);padding:6px 12px;pointer-events:none;position:fixed;transform:translateX(-50%);z-index:860}.popped-out{align-items:center;background-color:var(--console-bg);color:var(--console-text);display:flex;inset:0;justify-content:center;position:fixed;z-index:950}.popped-out-content{align-items:center;display:flex;flex-direction:column;gap:8px;padding:16px;text-align:center}.popped-out-title{font-size:15px;font-weight:600;margin-top:8px}.popped-out-text{color:var(--muted-text);font-size:13px}@keyframes fade-in{from{opacity:0}}@keyframes dialog-in{from{opacity:0;transform:scale(.96)}}.rotated-90deg{transform:rotate(90deg)}.align-top{vertical-align:top!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.no-select{-webkit-user-select:none;user-select:none}:root{color-scheme:light;--console-font:ui-monospace,Menlo,Monaco,Consolas,'Liberation Mono','Courier New',monospace;--console-bg:#fff;--console-text:#1f1f1f;--row-border:#f0f0f0;--arrow:#727272;--chevron:#9aa0a6;--entry-key:#881391;--preview-key:#5f6368;--string:#c41a16;--number:#1a1aa6;--null:#80868b;--header-bg:#f1f3f4;--header-text:#3c4043;--warn-bg:#fffbe5;--warn-border:#fff5c2;--warn-text:#5c3c00;--warn-icon:#e8a600;--error-bg:#fff0f0;--error-border:#ffd6d6;--error-text:#dc362e;--error-icon:#dc362e;--table-header-bg:#f3f3f3;--table-border:#d0d0d0;--toolbar-bg:#f3f3f3;--toolbar-border:#d6d6d6;--toolbar-text:#333;--muted-text:#5f6368;--button-hover:rgb(0 0 0 / 0.08);--accent:#1a73e8;--accent-hover:#1765cc;--accent-text:#fff;--divider-bg:#f3f3f3;--divider-hover-bg:#e8eaed;--divider-grip:#b0b0b0;--dialog-bg:#fff;--backdrop:rgb(0 0 0 / 0.25)}:root[data-theme=dark]{color-scheme:dark;--console-bg:#242424;--console-text:#e3e3e3;--row-border:#3a3a3a;--arrow:#9aa0a6;--chevron:#80868b;--entry-key:#5db0d7;--preview-key:#9aa0a6;--string:#f28b54;--number:#9980ff;--null:#8e8e8e;--header-bg:#2d2e30;--header-text:#c4c7c5;--warn-bg:#332b00;--warn-border:#665500;--warn-text:#ffd17a;--warn-icon:#ffd17a;--error-bg:#290000;--error-border:#5c0000;--error-text:#ff8080;--error-icon:#ff6b6b;--table-header-bg:#2e2e2e;--table-border:#4a4a4a;--toolbar-bg:#2b2b2b;--toolbar-border:#474747;--toolbar-text:#e3e3e3;--muted-text:#9aa0a6;--button-hover:rgb(255 255 255 / 0.1);--accent:#8ab4f8;--accent-hover:#aecbfa;--accent-text:#202124;--divider-bg:#2b2b2b;--divider-hover-bg:#333;--divider-grip:#6b6b6b;--dialog-bg:#2d2e30;--backdrop:rgb(0 0 0 / 0.5)}#displays{background-color:var(--console-bg);color:var(--console-text)}#displays>div{font-family:var(--console-font);font-size:calc(12px * var(--text-scale, 1));line-height:calc(16px * var(--text-scale, 1))}.console-row{border-bottom:1px solid var(--row-border);padding:2px 8px 2px 24px;position:relative;white-space:pre-wrap;word-break:break-word}.console-row>.row-icon{left:6px;line-height:0;position:absolute;top:4px}.log-error,.log-warn{margin-top:-1px}.log-warn{background-color:var(--warn-bg);border-bottom-color:var(--warn-border);border-top:1px solid var(--warn-border);color:var(--warn-text)}.log-warn>.row-icon{color:var(--warn-icon)}.log-error{background-color:var(--error-bg);border-bottom-color:var(--error-border);border-top:1px solid var(--error-border);color:var(--error-text)}.log-error>.row-icon{color:var(--error-icon)}.console-row{padding-left:calc(24px + var(--depth,0) * 16px)}.console-row>.row-icon{left:calc(6px + var(--depth,0) * 16px)}.group-header{cursor:pointer;font-weight:700}.group-header>.row-icon.arrow{top:5px}.log-assert{background-color:var(--error-bg);border-bottom-color:var(--error-border);border-top:1px solid var(--error-border);color:var(--error-text);margin-top:-1px}.log-assert>.row-icon{color:var(--error-icon)}.log-result>.row-icon{color:var(--chevron)}.trace-stack{color:var(--null);padding-left:1ch}.splash-message{color:var(--muted-text);font-size:13px;max-width:280px;text-align:center}.splash-slow{animation:show-after-delay 0s 10s both}@keyframes show-after-delay{from{visibility:hidden}to{visibility:visible}}#splash.failed .splash-progress,#splash.failed .splash-slow,.splash-error{display:none}#splash.failed .splash-error{display:block}.code-header{background-color:var(--header-bg);border-bottom:1px solid var(--row-border);color:var(--header-text);font-weight:700;padding:2px 8px;white-space:pre-wrap}.code-header.toggleable{cursor:pointer}.code-row .copy-to-editor-button{align-items:center;background-color:var(--console-bg);border:1px solid var(--toolbar-border);border-radius:4px;color:var(--toolbar-text);cursor:pointer;display:inline-flex;height:22px;justify-content:center;opacity:0;padding:0;position:absolute;right:6px;top:2px;transition:opacity .15s;width:22px}.code-row .copy-to-editor-button:focus-visible,.code-row:hover .copy-to-editor-button{opacity:1}.code-row .copy-to-editor-button:hover{background-color:var(--toolbar-bg)}@media (hover:none){.code-row .copy-to-editor-button{opacity:.8}}.notice{color:var(--null);font-style:italic}.code-row>.row-icon{color:var(--chevron)}.code-row code,.code-row pre{background:0 0!important;font-family:var(--console-font)!important;font-size:calc(12px * var(--text-scale, 1))!important;line-height:calc(16px * var(--text-scale, 1))!important;padding:0!important;text-shadow:none!important}.js-value{max-width:100%;vertical-align:top}.row-content>.js-value+.js-value{margin-left:1ch}.js-value-header.expandable{cursor:default}.js-value .expansion{padding-left:12px}.js-value .loading{color:var(--null);padding-left:12px}.arrow{display:inline-block;height:10px;position:relative;width:12px}.arrow.expandable::before{border-color:transparent transparent transparent var(--arrow);border-style:solid;border-width:4px 0 4px 6px;content:'';left:2px;position:absolute;top:1px;transform-origin:3px 4px;transition:transform .1s}.arrow.expandable.expanded::before{transform:rotate(90deg)}.entry-key{color:var(--entry-key)}.entry-key.dim{opacity:.6}.t-key{color:var(--preview-key)}.t-regexp,.t-string,.t-symbol{color:var(--string)}.t-number{color:var(--number)}.t-null{color:var(--null)}.t-function{font-style:italic}.t-node{color:var(--entry-key)}.console-table{border:1px solid var(--table-border);border-collapse:collapse;margin:2px 0 4px;white-space:nowrap}.console-table td,.console-table th{border-left:1px solid var(--table-border);max-width:300px;overflow:hidden;padding:1px 4px;text-align:left;text-overflow:ellipsis}.console-table th{background-color:var(--table-header-bg);border-bottom:1px solid var(--table-border);font-weight:400}";
|
|
7
|
+
const VIEWER_IFRAME_CSS = "[v-cloak]{display:none}body,html{height:100%;margin:0}body{background-color:var(--console-bg);font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.svg-defs{height:0;position:absolute;width:0}#vueApp{display:flex;flex-direction:column;inset:0;position:fixed}#main{display:grid;flex-grow:1;gap:0;min-height:0;position:relative}#main.is-moving-divider{-webkit-user-select:none;user-select:none}#main.col-orient.is-moving-divider{cursor:col-resize}#main.row-orient.is-moving-divider{cursor:row-resize}#main.is-moving-divider::after{background-color:var(--accent);content:'';position:absolute;z-index:99}#main.col-orient.is-moving-divider::after{bottom:0;left:calc(100% - var(--temp-editor-pct) - var(--divider-size)/ 2 - 1px);top:0;width:2px}#main.row-orient.is-moving-divider::after{height:2px;left:0;right:0;top:calc(100% - var(--temp-editor-pct) - var(--divider-size)/ 2 - 1px)}#main.col-orient{grid-template-columns:1fr var(--divider-size) calc(var(--editor-pct) - var(--divider-size)/ 2);grid-template-rows:1fr}#main.row-orient{grid-template-columns:1fr;grid-template-rows:1fr var(--divider-size) calc(var(--editor-pct) - var(--divider-size)/ 2)}#main.is-output-hidden{grid-template-columns:1fr;grid-template-rows:1fr}#main.is-output-hidden>#displays,#main.is-output-hidden>.divider{display:none}.divider{background-color:var(--divider-bg);box-sizing:border-box;position:relative;transition:background-color .15s}#main.col-orient .divider{border-left:1px solid var(--toolbar-border);border-right:1px solid var(--toolbar-border);cursor:col-resize}#main.row-orient .divider{border-bottom:1px solid var(--toolbar-border);border-top:1px solid var(--toolbar-border);cursor:row-resize}.divider::after{background-color:var(--divider-grip);border-radius:2px;content:'';left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);transition:background-color .15s}#main.col-orient .divider::after{height:32px;width:2px}#main.row-orient .divider::after{height:2px;width:32px}#main.is-moving-divider .divider,.divider:hover{background-color:var(--divider-hover-bg)}#main.is-moving-divider .divider::after,.divider:hover::after{background-color:var(--accent)}#displays{position:relative}#displays>div{position:absolute;inset:0;overflow:auto}#bottomNav{align-items:center;background-color:var(--toolbar-bg);border-top:1px solid var(--toolbar-border);color:var(--toolbar-text);display:flex;flex:0 0 auto;font-size:12px;gap:8px;height:32px;padding:0 6px 0 8px;-webkit-user-select:none;user-select:none}#bottomNav>.brand{align-items:center;display:flex;flex-grow:1;gap:8px}.runner-badge{border:1px solid var(--toolbar-border);border-radius:999px;color:var(--muted-text);font-size:10px;letter-spacing:.04em;line-height:15px;padding:0 6px;text-transform:uppercase}#bottomNav>.buttons{align-items:center;display:flex;gap:2px}#bottomNav .separator{background-color:var(--toolbar-border);height:16px;margin:0 4px;width:1px}#bottomNav button{align-items:center;background:0 0;border:0;border-radius:4px;color:inherit;cursor:pointer;display:inline-flex;font:inherit;gap:5px;height:24px;justify-content:center;min-width:26px;padding:0 6px}#bottomNav button>span:not(.label){display:inline-flex;font-size:14px}#bottomNav button svg:not(.spin){transform:none!important}#bottomNav button:hover:not(:disabled){background-color:var(--button-hover)}#bottomNav button:disabled{cursor:not-allowed;opacity:.45}#bottomNav button.primary{background-color:var(--accent);color:var(--accent-text);font-weight:600;margin-left:2px;padding:0 10px 0 8px}#bottomNav button.primary>span:not(.label){font-size:11px}#bottomNav button.primary:hover:not(:disabled){background-color:var(--accent-hover)}#bottomNav button:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.spin{animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.logo{align-items:center;color:var(--toolbar-text);display:inline-flex;font-weight:700;gap:5px;letter-spacing:-.01em;line-height:1}.logo-mark{flex:none;height:18px;width:18px}.logo-text{font-size:14px;white-space:nowrap}@media (max-width:440px){#bottomNav .logo-text{display:none}}.logo-your{font-weight:400;opacity:.8}.logo-large{gap:12px}.logo-large>.logo-mark{filter:drop-shadow(0 6px 12px rgb(0 0 0 / .18));height:56px;width:56px}.logo-large>.logo-text{font-size:36px}#splash{align-items:center;background-color:var(--console-bg);display:flex;inset:0;justify-content:center;position:fixed;transition:opacity .35s ease,visibility .35s;z-index:1000}#splash.hidden{opacity:0;visibility:hidden}.splash-content{align-items:center;animation:splash-in .4s ease-out both;display:flex;flex-direction:column;gap:24px}.splash-progress{background-color:var(--toolbar-border);border-radius:3px;height:3px;overflow:hidden;width:140px}.splash-progress>div{animation:splash-progress 1.1s ease-in-out infinite;background-color:var(--accent);border-radius:inherit;height:100%;width:40%}@keyframes splash-in{from{opacity:0;transform:translateY(6px)}}@keyframes splash-progress{from{transform:translateX(-100%)}to{transform:translateX(250%)}}.dialog-backdrop{align-items:center;animation:fade-in .12s ease-out;background-color:var(--backdrop);display:flex;inset:0;justify-content:center;position:fixed;z-index:900}.dialog{animation:dialog-in .15s ease-out;background-color:var(--dialog-bg);border:1px solid var(--toolbar-border);border-radius:8px;box-shadow:0 12px 40px rgb(0 0 0 / .3);color:var(--console-text);font-size:13px;padding:16px;width:min(360px,calc(100% - 32px))}.dialog-title{font-size:14px;font-weight:600;margin-bottom:6px}.dialog-message{color:var(--muted-text);line-height:1.45}.dialog-buttons{display:flex;gap:8px;justify-content:flex-end;margin-top:16px}.dialog-button{background:0 0;border:1px solid var(--toolbar-border);border-radius:4px;color:inherit;cursor:pointer;font:inherit;height:28px;padding:0 12px}.dialog-button:hover{background-color:var(--button-hover)}.dialog-button.primary{background-color:var(--accent);border-color:var(--accent);color:var(--accent-text);font-weight:600}.dialog-button.primary:hover{background-color:var(--accent-hover)}.dialog-button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}#bottomNav button.logo-button{margin-left:-4px;padding:0 4px}.about-dialog{display:flex;flex-direction:column;max-height:calc(100% - 24px);padding:0;width:min(620px,calc(100% - 24px))}.about-dialog.is-export{height:min(560px,calc(100% - 24px))}.about-header{align-items:center;border-bottom:1px solid var(--toolbar-border);display:flex;flex:0 0 auto;padding:0 8px 0 12px}.tabs{display:flex;flex-grow:1;gap:4px}.tab{background:0 0;border:0;border-bottom:2px solid transparent;color:var(--muted-text);cursor:pointer;font:inherit;font-weight:600;padding:10px 8px 8px}.tab:hover{color:var(--console-text)}.tab.active{border-bottom-color:var(--accent);color:var(--console-text)}.close-button{align-items:center;background:0 0;border:0;border-radius:4px;color:var(--muted-text);cursor:pointer;display:inline-flex;font-size:16px;height:28px;justify-content:center;width:28px}.close-button:hover{background-color:var(--button-hover);color:var(--console-text)}.close-button:focus-visible,.segmented>button:focus-visible,.tab:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}.about-body{flex:1 1 auto;min-height:0;overflow:auto;padding:16px}.about-title{align-items:center;display:flex;gap:12px}.logo-medium>.logo-mark{height:40px;width:40px}.logo-medium>.logo-text{display:none}.about-name{font-size:17px;font-weight:700}.about-version{color:var(--muted-text);font-size:12px}.about-description{line-height:1.5;margin:12px 0}.about-links{display:flex;flex-wrap:wrap;gap:6px 16px}.about-links a{color:var(--accent);font-weight:600;text-decoration:none}.about-links a:hover{text-decoration:underline}.about-body h3{color:var(--muted-text);font-size:11px;letter-spacing:.05em;margin:18px 0 6px;text-transform:uppercase}.about-details{display:grid;gap:4px 16px;grid-template-columns:max-content 1fr;margin:0}.about-details dt{color:var(--muted-text)}.about-details dd{margin:0}.about-details kbd{background-color:var(--toolbar-bg);border:1px solid var(--toolbar-border);border-bottom-width:2px;border-radius:4px;font-family:inherit;font-size:11px;padding:0 4px}.about-footer{border-top:1px solid var(--toolbar-border);color:var(--muted-text);font-size:12px;margin-top:18px;padding-top:12px}.export-body{display:flex;flex-direction:column;gap:10px}.export-options{display:flex;flex-wrap:wrap;gap:8px;justify-content:space-between}.segmented{border:1px solid var(--toolbar-border);border-radius:6px;display:inline-flex;overflow:hidden}.segmented>button{background:0 0;border:0;color:var(--muted-text);cursor:pointer;font:inherit;font-size:12px;font-weight:600;padding:5px 10px}.segmented>button+button{border-left:1px solid var(--toolbar-border)}.segmented>button:hover{background-color:var(--button-hover)}.segmented>button.active{background-color:var(--accent);color:var(--accent-text)}.export-note{color:var(--muted-text);font-size:12px}.export-preview{border:1px solid var(--toolbar-border);border-radius:6px;flex:1 1 auto;min-height:120px;overflow:hidden}.export-actions{display:flex;gap:8px;justify-content:flex-end}.file-input{display:none}.more-menu{background-color:var(--dialog-bg);border:1px solid var(--toolbar-border);border-radius:8px;bottom:38px;box-shadow:0 8px 28px rgb(0 0 0 / .25);color:var(--console-text);font-size:13px;max-height:calc(100% - 48px);min-width:236px;overflow:auto;padding:4px;position:absolute;right:6px;z-index:800}.menu-item{align-items:center;background:0 0;border:0;border-radius:5px;color:inherit;cursor:pointer;display:flex;font:inherit;gap:8px;padding:6px 8px;text-align:left;width:100%}.menu-item:focus-visible,.menu-item:hover:not(:disabled){background-color:var(--button-hover);outline:0}.menu-item:disabled{cursor:not-allowed;opacity:.4}.menu-shortcut{color:var(--muted-text);font-size:11px;margin-left:auto;padding-left:16px}.menu-icon{color:var(--muted-text);display:inline-flex;font-size:14px;justify-content:center;width:16px}.menu-icon svg{transform:none!important}.menu-row{align-items:center;display:flex;gap:2px;padding:2px 2px 2px 8px}.menu-row-label{flex-grow:1}.menu-step{font-weight:600;justify-content:center;width:auto}.menu-percent{color:var(--muted-text);font-variant-numeric:tabular-nums;font-weight:400;min-width:52px}.menu-separator{background-color:var(--toolbar-border);height:1px;margin:4px 0}.history-panel{max-width:min(420px,calc(100% - 12px));width:360px}.history-title{color:var(--muted-text);font-size:11px;font-weight:600;letter-spacing:.05em;padding:4px 8px 6px;text-transform:uppercase}.history-item{justify-content:space-between}.history-label{font-family:var(--console-font);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.history-detail{color:var(--muted-text);flex-shrink:0;font-size:11px}.history-empty,.history-hint{color:var(--muted-text);font-size:12px;padding:6px 8px}.history-hint{border-top:1px solid var(--toolbar-border);margin-top:4px}.value-menu{bottom:auto;min-width:220px;position:fixed;right:auto;z-index:850}.toast{animation:fade-in .15s ease-out;background-color:var(--console-text);border-radius:6px;bottom:44px;box-shadow:0 6px 20px rgb(0 0 0 / .25);color:var(--console-bg);font-size:12px;left:50%;max-width:calc(100% - 32px);padding:6px 12px;pointer-events:none;position:fixed;transform:translateX(-50%);z-index:860}.popped-out{align-items:center;background-color:var(--console-bg);color:var(--console-text);display:flex;inset:0;justify-content:center;position:fixed;z-index:950}.popped-out-content{align-items:center;display:flex;flex-direction:column;gap:8px;padding:16px;text-align:center}.popped-out-title{font-size:15px;font-weight:600;margin-top:8px}.popped-out-text{color:var(--muted-text);font-size:13px}@keyframes fade-in{from{opacity:0}}@keyframes dialog-in{from{opacity:0;transform:scale(.96)}}.rotated-90deg{transform:rotate(90deg)}.align-top{vertical-align:top!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.no-select{-webkit-user-select:none;user-select:none}:root{color-scheme:light;--console-font:ui-monospace,Menlo,Monaco,Consolas,'Liberation Mono','Courier New',monospace;--console-bg:#fff;--console-text:#1f1f1f;--row-border:#f0f0f0;--arrow:#727272;--chevron:#9aa0a6;--entry-key:#881391;--preview-key:#5f6368;--string:#c41a16;--number:#1a1aa6;--null:#80868b;--header-bg:#f1f3f4;--header-text:#3c4043;--warn-bg:#fffbe5;--warn-border:#fff5c2;--warn-text:#5c3c00;--warn-icon:#e8a600;--error-bg:#fff0f0;--error-border:#ffd6d6;--error-text:#dc362e;--error-icon:#dc362e;--table-header-bg:#f3f3f3;--table-border:#d0d0d0;--toolbar-bg:#f3f3f3;--toolbar-border:#d6d6d6;--toolbar-text:#333;--muted-text:#5f6368;--button-hover:rgb(0 0 0 / 0.08);--accent:#1a73e8;--accent-hover:#1765cc;--accent-text:#fff;--divider-bg:#f3f3f3;--divider-hover-bg:#e8eaed;--divider-grip:#b0b0b0;--dialog-bg:#fff;--backdrop:rgb(0 0 0 / 0.25)}:root[data-theme=dark]{color-scheme:dark;--console-bg:#242424;--console-text:#e3e3e3;--row-border:#3a3a3a;--arrow:#9aa0a6;--chevron:#80868b;--entry-key:#5db0d7;--preview-key:#9aa0a6;--string:#f28b54;--number:#9980ff;--null:#8e8e8e;--header-bg:#2d2e30;--header-text:#c4c7c5;--warn-bg:#332b00;--warn-border:#665500;--warn-text:#ffd17a;--warn-icon:#ffd17a;--error-bg:#290000;--error-border:#5c0000;--error-text:#ff8080;--error-icon:#ff6b6b;--table-header-bg:#2e2e2e;--table-border:#4a4a4a;--toolbar-bg:#2b2b2b;--toolbar-border:#474747;--toolbar-text:#e3e3e3;--muted-text:#9aa0a6;--button-hover:rgb(255 255 255 / 0.1);--accent:#8ab4f8;--accent-hover:#aecbfa;--accent-text:#202124;--divider-bg:#2b2b2b;--divider-hover-bg:#333;--divider-grip:#6b6b6b;--dialog-bg:#2d2e30;--backdrop:rgb(0 0 0 / 0.5)}#displays{background-color:var(--console-bg);color:var(--console-text)}#displays>div{font-family:var(--console-font);font-size:calc(12px * var(--text-scale, 1));line-height:calc(16px * var(--text-scale, 1))}.console-row{border-bottom:1px solid var(--row-border);padding:2px 8px 2px 24px;position:relative;white-space:pre-wrap;word-break:break-word}.console-row>.row-icon{left:6px;line-height:0;position:absolute;top:4px}.console-row>.row-icon.repeat-count{background-color:var(--null);border-radius:7px;box-sizing:border-box;color:var(--console-bg);font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;font-size:10px;height:14px;left:calc(3px + var(--depth,0) * 16px);line-height:14px;min-width:16px;padding:0 4px;text-align:center;top:3px}.log-warn>.row-icon.repeat-count{background-color:var(--warn-icon)}.log-assert>.row-icon.repeat-count,.log-error>.row-icon.repeat-count{background-color:var(--error-icon)}.log-error,.log-warn{margin-top:-1px}.log-warn{background-color:var(--warn-bg);border-bottom-color:var(--warn-border);border-top:1px solid var(--warn-border);color:var(--warn-text)}.log-warn>.row-icon{color:var(--warn-icon)}.log-error{background-color:var(--error-bg);border-bottom-color:var(--error-border);border-top:1px solid var(--error-border);color:var(--error-text)}.log-error>.row-icon{color:var(--error-icon)}.console-row{padding-left:calc(24px + var(--depth,0) * 16px)}.console-row>.row-icon{left:calc(6px + var(--depth,0) * 16px)}.group-header{cursor:pointer;font-weight:700}.group-header>.row-icon.arrow{top:5px}.log-assert{background-color:var(--error-bg);border-bottom-color:var(--error-border);border-top:1px solid var(--error-border);color:var(--error-text);margin-top:-1px}.log-assert>.row-icon{color:var(--error-icon)}.log-result>.row-icon{color:var(--chevron)}.trace-stack{color:var(--null);padding-left:1ch}.splash-message{color:var(--muted-text);font-size:13px;max-width:280px;text-align:center}.splash-slow{animation:show-after-delay 0s 10s both}@keyframes show-after-delay{from{visibility:hidden}to{visibility:visible}}#splash.failed .splash-progress,#splash.failed .splash-slow,.splash-error{display:none}#splash.failed .splash-error{display:block}.code-header{background-color:var(--header-bg);border-bottom:1px solid var(--row-border);color:var(--header-text);font-weight:700;padding:2px 8px;white-space:pre-wrap}.code-header.toggleable{cursor:pointer}.code-row .copy-to-editor-button{align-items:center;background-color:var(--console-bg);border:1px solid var(--toolbar-border);border-radius:4px;color:var(--toolbar-text);cursor:pointer;display:inline-flex;height:22px;justify-content:center;opacity:0;padding:0;position:absolute;right:6px;top:2px;transition:opacity .15s;width:22px}.code-row .copy-to-editor-button:focus-visible,.code-row:hover .copy-to-editor-button{opacity:1}.code-row .copy-to-editor-button:hover{background-color:var(--toolbar-bg)}@media (hover:none){.code-row .copy-to-editor-button{opacity:.8}}.notice{color:var(--null);font-style:italic}.code-row>.row-icon{color:var(--chevron)}.code-row code,.code-row pre{background:0 0!important;font-family:var(--console-font)!important;font-size:calc(12px * var(--text-scale, 1))!important;line-height:calc(16px * var(--text-scale, 1))!important;padding:0!important;text-shadow:none!important}.js-value{max-width:100%;vertical-align:top}.row-content>.js-value+.js-value{margin-left:1ch}.row-content>.js-value+.js-value.is-joined{margin-left:0}.js-value-header.expandable{cursor:default}.js-value .expansion{padding-left:12px}.js-value .loading{color:var(--null);padding-left:12px}.arrow{display:inline-block;height:10px;position:relative;width:12px}.arrow.expandable::before{border-color:transparent transparent transparent var(--arrow);border-style:solid;border-width:4px 0 4px 6px;content:'';left:2px;position:absolute;top:1px;transform-origin:3px 4px;transition:transform .1s}.arrow.expandable.expanded::before{transform:rotate(90deg)}.entry-key{color:var(--entry-key)}.entry-key.dim{opacity:.6}.t-key{color:var(--preview-key)}.t-regexp,.t-string,.t-symbol{color:var(--string)}.t-number{color:var(--number)}.t-null{color:var(--null)}.t-function{font-style:italic}.t-node{color:var(--entry-key)}.console-table{border:1px solid var(--table-border);border-collapse:collapse;margin:2px 0 4px;white-space:nowrap}.console-table td,.console-table th{border-left:1px solid var(--table-border);max-width:300px;overflow:hidden;padding:1px 4px;text-align:left;text-overflow:ellipsis}.console-table th{background-color:var(--table-header-bg);border-bottom:1px solid var(--table-border);font-weight:400}";
|
|
8
8
|
/**
|
|
9
9
|
* Information about this package (eg. its version).
|
|
10
10
|
* @type {{name: string, version: string, homepage: string, repoUrl: string, bugsUrl: string}}
|
|
11
11
|
*/
|
|
12
|
-
const PACKAGE_INFO = {"name":"yourjs-box","version":"1.
|
|
12
|
+
const PACKAGE_INFO = {"name":"yourjs-box","version":"1.10.0","homepage":"https://westc.github.io/yourjs-box/","repoUrl":"https://github.com/westc/yourjs-box","bugsUrl":"https://github.com/westc/yourjs-box/issues"};
|
|
13
13
|
/**
|
|
14
14
|
* Viewer IFRAME's HTML code
|
|
15
15
|
* @type {string}
|
|
16
16
|
*/
|
|
17
|
-
const VIEWER_IFRAME_HTML = "<div id=\"splash\" aria-label=\"Loading\"><div class=\"splash-content\"><span class=\"logo logo-large\"><span class=\"logo-mark\">JS</span><span class=\"logo-text\">Box</span></span><div class=\"splash-progress\"><div></div></div><div class=\"splash-message splash-slow\">Still loading…</div><div class=\"splash-message splash-error\">JS Box couldn’t load. Please check your connection and reload the page.</div></div></div><div id=\"vueApp\" v-cloak><input type=\"file\" ref=\"fileInput\" class=\"file-input\" accept=\".js,.mjs,.cjs,.ts,.mts,.cts,.txt,text/javascript,text/plain\" tabindex=\"-1\" aria-hidden=\"true\" @change=\"onFileChosen\"><div id=\"main\" ref=\"main\" :class=\"mainElemClassNames\" :style=\"mainElemStyles\" @mousedown=\"onMainElemMouseDown\"><div id=\"displays\"><div ref=\"displaysScroller\" @scroll=\"onDisplaysScroll\"><template v-for=\"display in displays\"><div v-if=\"display.type === 'prism'\"><div v-if=\"display.isHidden\" class=\"code-header toggleable no-select\" @click=\"display.isCodeShown = !display.isCodeShown\" :title=\"display.isCodeShown ? 'Hide Code' : 'Show Code'\"><span :class=\"['arrow', 'expandable', display.isCodeShown ? 'expanded' : '']\"></span>{{ display.header || 'Hidden code' }}</div><div v-else-if=\"display.header\" class=\"code-header\">{{ display.header }}</div><div v-if=\"display.isCodeShown\" class=\"console-row code-row\"><span class=\"row-icon\"><icon name=\"chevron\"></icon></span><prism :language=\"language\" :code=\"display.value\" :is-dark=\"theme === 'dark'\" match-braces></prism><button class=\"copy-to-editor-button\" title=\"Copy to editor\" @click=\"copyToEditor(display.value)\"><icon name=\"copyToEditor\"></icon></button></div></div><div v-if=\"display.type === 'log' && !isInCollapsedGroup(display)\" :class=\"['console-row', 'log-' + display.key, display.groupId ? 'group-header' : '']\" :style=\"{'--depth': display.groupIds.length}\" :title=\"display.name\" @click=\"onLogRowClick(display, $event)\"><span v-if=\"display.key === 'error' || display.key === 'assert'\" class=\"row-icon\"><icon name=\"consoleError\"></icon></span><span v-else-if=\"display.key === 'warn'\" class=\"row-icon\"><icon name=\"consoleWarning\"></icon></span><span v-else-if=\"display.key === 'result'\" class=\"row-icon\"><icon name=\"result\"></icon></span><span v-else-if=\"display.groupId\" :class=\"['row-icon', 'arrow', 'expandable', display.isCollapsed ? '' : 'expanded']\"></span><div class=\"row-content\"><table v-if=\"display.table\" class=\"console-table\"><thead><tr><th v-for=\"header in display.table.headers\">{{ header }}</th></tr></thead><tbody><tr v-for=\"row in display.table.rows\"><td>{{ row.index }}</td><td v-for=\"cell in row.cells\"><template v-if=\"cell\"><span v-for=\"part in cell.parts\" :class=\"'t-' + part[0]\">{{ part[1] }}</span></template></td></tr></tbody></table><js-value v-for=\"(description, index) in display.descriptions\" :description=\"description\" :key-path=\"[]\" :path=\"[display.logId, index]\"></js-value><div v-if=\"display.stack\" class=\"trace-stack\">{{ display.stack }}</div></div></div><div v-if=\"display.type === 'notice'\" class=\"console-row notice\">{{ display.message }}</div><div v-if=\"display.type === 'error'\" class=\"console-row log-error\"><span class=\"row-icon\"><icon name=\"consoleError\"></icon></span><div class=\"row-content\">{{ display.message }}</div></div></template></div></div><div class=\"divider\" ref=\"mainDivider\"></div><div id=\"editor\"><ace-editor ref=\"editor\" v-model=\"jsCode\" :language=\"language\" :theme=\"theme\" :font-size=\"Math.round(12 * textScale)\" @key-combo=\"onEditorKeyCombo\" @history-key=\"onEditorHistoryKey\" height=\"100%\"></ace-editor></div></div><div id=\"bottomNav\"><div class=\"brand\"><button class=\"logo-button\" title=\"About JS Box\" @click=\"openAbout('about')\"><span class=\"logo\"><span class=\"logo-mark\">JS</span><span class=\"logo-text\">Box</span></span></button> <span class=\"runner-badge\" :title=\"runnerMode === 'window' ? 'Code runs directly in this page' : 'Code runs in a Web Worker (no DOM access)'\">{{ runnerMode === 'window' ? 'Window' : 'Worker' }}</span></div><div class=\"buttons\"><template v-for=\"bottomButton in bottomButtons\"><span v-if=\"bottomButton.isSeparator\" class=\"separator\"></span> <button v-else :class=\"bottomButton.className\" @click=\"bottomButton.callback.call(this, $event)\" :title=\"bottomButton.title\" :disabled=\"bottomButton.disableIf && bottomButton.disableIf.call(this)\"><icon :name=\"bottomButton.iconName\"></icon><span v-if=\"bottomButton.label\" class=\"label\">{{ bottomButton.label }}</span></button></template></div></div><div v-if=\"isMenuOpen\" class=\"more-menu\" role=\"menu\" aria-label=\"More\" ref=\"moreMenu\" @keydown=\"onMenuKeyDown\"><div class=\"menu-row\" role=\"group\" aria-label=\"Text size\"><span class=\"menu-row-label\">Text size</span> <button class=\"menu-item menu-step\" role=\"menuitem\" title=\"Smaller text\" aria-label=\"Smaller text\" :disabled=\"!canShrinkText\" @click=\"changeTextScale(-1)\">A−</button> <button class=\"menu-item menu-step menu-percent\" role=\"menuitem\" title=\"Reset the text size\" aria-label=\"Reset the text size\" @click=\"setTextScale(1)\">{{ Math.round(textScale * 100) }}%</button> <button class=\"menu-item menu-step\" role=\"menuitem\" title=\"Bigger text\" aria-label=\"Bigger text\" :disabled=\"!canGrowText\" @click=\"changeTextScale(1)\">A+</button></div><div class=\"menu-separator\"></div><button class=\"menu-item\" role=\"menuitem\" @click=\"openFile()\"><span class=\"menu-icon\"><icon name=\"open\"></icon></span>Open…</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"saveFile()\"><span class=\"menu-icon\"><icon name=\"save\"></icon></span>Save…</button><div class=\"menu-separator\"></div><button v-if=\"isPopOut\" class=\"menu-item\" role=\"menuitem\" @click=\"closeMenu(); bringBack()\"><span class=\"menu-icon\"><icon name=\"popIn\"></icon></span>Bring back into the page</button> <button v-else class=\"menu-item\" role=\"menuitem\" @click=\"popOut()\"><span class=\"menu-icon\"><icon name=\"popOut\"></icon></span>Pop out into a window</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"closeMenu(); openAbout('html')\"><span class=\"menu-icon\"><icon name=\"code\"></icon></span>Copy as HTML…</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"closeMenu(); resetConsole()\"><span class=\"menu-icon\"><icon name=\"refresh\"></icon></span>Reset…</button><div class=\"menu-separator\"></div><button class=\"menu-item\" role=\"menuitem\" @click=\"closeMenu(); openAbout('about')\"><span class=\"menu-icon\"><icon name=\"info\"></icon></span>About JS Box</button></div><div v-if=\"isHistoryOpen\" class=\"more-menu history-panel\" role=\"menu\" aria-label=\"History\" ref=\"historyPanel\" @keydown=\"onMenuKeyDown\"><div class=\"history-title\">History</div><div v-if=\"!historyEntries.length\" class=\"history-empty\">Code that you run will show up here.</div><button v-for=\"entry in historyEntries\" class=\"menu-item history-item\" role=\"menuitem\" :title=\"commandHistory[entry.index]\" @click=\"recallHistory(entry.index)\"><span class=\"history-label\">{{ entry.label }}</span> <span class=\"history-detail\">{{ entry.detail }}</span></button><div class=\"history-hint\">Tip: press ↑ at the start of the editor (or ↓ at the end) to go through the history.</div></div><div v-if=\"valueMenu\" class=\"more-menu value-menu\" role=\"menu\" aria-label=\"Value\" ref=\"valueMenu\" :style=\"{left: valueMenu.x + 'px', top: valueMenu.y + 'px'}\" @keydown=\"onMenuKeyDown\"><button class=\"menu-item\" role=\"menuitem\" @click=\"copyValueAsJson()\"><span class=\"menu-icon\"><icon name=\"copy\"></icon></span>Copy as JSON</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"saveValueAsJson()\"><span class=\"menu-icon\"><icon name=\"save\"></icon></span>Save as JSON…</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"storeValueAsGlobal()\"><span class=\"menu-icon\"><icon name=\"variable\"></icon></span>Store as global variable</button> <button v-if=\"valueMenu.propertyPath\" class=\"menu-item\" role=\"menuitem\" @click=\"copyPropertyPath()\"><span class=\"menu-icon\"><icon name=\"path\"></icon></span>Copy property path</button><template v-if=\"!valueMenu.isPrimitive\"><div class=\"menu-separator\"></div><button class=\"menu-item\" role=\"menuitem\" @click=\"refreshValue()\"><span class=\"menu-icon\"><icon name=\"refresh\"></icon></span>Refresh</button></template></div><div v-if=\"toast\" class=\"toast\" role=\"status\">{{ toast.message }}</div><div v-if=\"isPoppedOut\" class=\"popped-out\"><div class=\"popped-out-content\"><span class=\"logo logo-medium\"><span class=\"logo-mark\">JS</span><span class=\"logo-text\">Box</span></span><div class=\"popped-out-title\">This console is open in a separate window</div><div class=\"popped-out-text\">Its code still runs in this page.</div><div class=\"dialog-buttons\"><button class=\"dialog-button\" @click=\"focusPopOut\">Show the window</button> <button class=\"dialog-button primary\" @click=\"requestPopIn\">Bring it back</button></div></div></div><div v-if=\"isAboutOpen\" class=\"dialog-backdrop\" @mousedown.self=\"closeAbout\" @keydown.esc=\"closeAbout\"><div :class=\"['dialog', 'about-dialog', aboutTab === 'html' ? 'is-export' : '']\" role=\"dialog\" aria-modal=\"true\" aria-label=\"About JS Box\"><div class=\"about-header\"><div class=\"tabs\" role=\"tablist\"><button role=\"tab\" :aria-selected=\"aboutTab === 'about'\" :class=\"['tab', aboutTab === 'about' ? 'active' : '']\" @click=\"aboutTab = 'about'\">About</button> <button role=\"tab\" :aria-selected=\"aboutTab === 'html'\" :class=\"['tab', aboutTab === 'html' ? 'active' : '']\" @click=\"aboutTab = 'html'\">Copy as HTML</button></div><button class=\"close-button\" ref=\"aboutCloseButton\" title=\"Close\" @click=\"closeAbout\"><icon name=\"close\"></icon></button></div><div v-if=\"aboutTab === 'about'\" class=\"about-body\"><div class=\"about-title\"><span class=\"logo logo-medium\"><span class=\"logo-mark\">JS</span><span class=\"logo-text\">Box</span></span><div><div class=\"about-name\">YourJS Box <span class=\"about-aka\">aka JS Box</span></div><div class=\"about-version\">Version {{ packageInfo.version }}</div></div></div><p class=\"about-description\">An interactive JavaScript console that can be embedded in any web page with a single script tag.</p><div class=\"about-links\"><a :href=\"packageInfo.homepage\" target=\"_blank\" rel=\"noopener\">Website</a> <a :href=\"packageInfo.repoUrl\" target=\"_blank\" rel=\"noopener\">GitHub</a> <a :href=\"packageInfo.repoUrl + '#readme'\" target=\"_blank\" rel=\"noopener\">Documentation</a> <a :href=\"packageInfo.bugsUrl\" target=\"_blank\" rel=\"noopener\">Report an issue</a></div><h3>This console</h3><dl class=\"about-details\"><dt>Code runs in</dt><dd>{{ runnerDescription }}</dd><dt>Theme</dt><dd>{{ themeDescription }}</dd><dt>Language</dt><dd>{{ isTypeScript ? 'TypeScript (types are removed by Babel, not checked)' : 'JavaScript' }}</dd><dt>Code blocks run as</dt><dd>{{ blockTypeDescription }}</dd><dt>Imports</dt><dd>{{ importsUrl ? 'Packages imported by name load from ' + importsUrl.replace('{specifier}', '\u2026') : 'Only URLs and paths can be imported' }}</dd><dt>Results</dt><dd>{{ showResults ? 'The value of the last expression is shown' : 'Not shown' }}</dd><dt>Layout</dt><dd>{{ layoutDescription }}</dd></dl><h3>Keyboard shortcuts</h3><dl class=\"about-details\"><dt><kbd>{{ modKey }}</kbd> + <kbd>Enter</kbd></dt><dd>Run the next block of code</dd><dt><kbd>↑</kbd> / <kbd>↓</kbd></dt><dd>Go through the code that was run (at the very start or end of the editor)</dd><dt><kbd>{{ modKey === 'Ctrl' ? 'Alt' : '⌥' }}</kbd> + <kbd>H</kbd></dt><dd>Show the history</dd><dt><kbd>Esc</kbd></dt><dd>Close this window, the menu or full screen</dd></dl><div class=\"about-footer\">MIT License © 2023-present Chris West · Built with {{ builtWith }}</div></div><div v-else class=\"about-body export-body\"><div class=\"export-options\"><div class=\"segmented\" role=\"radiogroup\" aria-label=\"Code\"><button v-for=\"option in exportCodeOptions\" role=\"radio\" :aria-checked=\"exportCode === option.value\" :class=\"exportCode === option.value ? 'active' : ''\" @click=\"exportCode = option.value\">{{ option.label }}</button></div><div class=\"segmented\" role=\"radiogroup\" aria-label=\"Format\"><button v-for=\"option in exportFormatOptions\" role=\"radio\" :aria-checked=\"exportFormat === option.value\" :class=\"exportFormat === option.value ? 'active' : ''\" @click=\"exportFormat = option.value\">{{ option.label }}</button></div></div><div class=\"export-note\">{{ exportNote }}</div><div class=\"export-preview\"><ace-editor :model-value=\"exportHtml\" language=\"html\" :theme=\"theme\" height=\"100%\" :read-only=\"true\"></ace-editor></div><div class=\"export-actions\"><button class=\"dialog-button\" @click=\"downloadExport\">Download page</button> <button class=\"dialog-button primary\" @click=\"copyExport\">{{ copyLabel }}</button></div></div></div></div><div v-if=\"dialog\" class=\"dialog-backdrop\" @mousedown.self=\"closeDialog(false)\" @keydown.esc=\"closeDialog(false)\"><div class=\"dialog\" role=\"alertdialog\" aria-modal=\"true\" :aria-label=\"dialog.title\"><div class=\"dialog-title\">{{ dialog.title }}</div><div class=\"dialog-message\">{{ dialog.message }}</div><div class=\"dialog-buttons\"><button class=\"dialog-button\" @click=\"closeDialog(false)\">Cancel</button> <button class=\"dialog-button primary\" ref=\"dialogConfirmButton\" @click=\"closeDialog(true)\">{{ dialog.confirmText }}</button></div></div></div></div>";
|
|
17
|
+
const VIEWER_IFRAME_HTML = "<svg class=\"svg-defs\" aria-hidden=\"true\"><!-- The logo: an open box whose front is the yellow JS square. --><symbol id=\"logo-mark\" viewBox=\"0 0 32 32\"><polygon points=\"1,10 8,3 31,3 24,10\" fill=\"#4a4a4a\"/><polygon points=\"8,3 31,3 29,5 10,5\" fill=\"#2e2e2e\"/><polygon points=\"24,10 31,3 31,24 24,31\" fill=\"#c7b000\"/><rect x=\"1\" y=\"10\" width=\"23\" height=\"21\" rx=\"1.5\" fill=\"#f7df1e\"/><text x=\"22\" y=\"28.5\" text-anchor=\"end\" font-size=\"11.5\" font-weight=\"800\" letter-spacing=\"-0.3\" fill=\"#1a1a1a\">JS</text></symbol></svg><div id=\"splash\" aria-label=\"Loading\"><div class=\"splash-content\"><span class=\"logo logo-large\"><svg class=\"logo-mark\"><use href=\"#logo-mark\"/></svg><span class=\"logo-text\"><span class=\"logo-your\">Your</span>JS Box</span></span><div class=\"splash-progress\"><div></div></div><div class=\"splash-message splash-slow\">Still loading…</div><div class=\"splash-message splash-error\">YourJS Box couldn’t load. Please check your connection and reload the page.</div></div></div><div id=\"vueApp\" v-cloak><input type=\"file\" ref=\"fileInput\" class=\"file-input\" accept=\".js,.mjs,.cjs,.ts,.mts,.cts,.txt,text/javascript,text/plain\" tabindex=\"-1\" aria-hidden=\"true\" @change=\"onFileChosen\"><div id=\"main\" ref=\"main\" :class=\"mainElemClassNames\" :style=\"mainElemStyles\" @mousedown=\"onMainElemMouseDown\"><div id=\"displays\"><div ref=\"displaysScroller\" @scroll=\"onDisplaysScroll\"><template v-for=\"display in displays\"><div v-if=\"display.type === 'prism'\"><div v-if=\"display.isHidden\" class=\"code-header toggleable no-select\" @click=\"display.isCodeShown = !display.isCodeShown\" :title=\"display.isCodeShown ? 'Hide Code' : 'Show Code'\"><span :class=\"['arrow', 'expandable', display.isCodeShown ? 'expanded' : '']\"></span>{{ display.header || 'Hidden code' }}</div><div v-else-if=\"display.header\" class=\"code-header\">{{ display.header }}</div><div v-if=\"display.isCodeShown\" class=\"console-row code-row\"><span class=\"row-icon\"><icon name=\"chevron\"></icon></span><prism :language=\"language\" :code=\"display.value\" :is-dark=\"theme === 'dark'\" match-braces></prism><button class=\"copy-to-editor-button\" title=\"Copy to editor\" @click=\"copyToEditor(display.value)\"><icon name=\"copyToEditor\"></icon></button></div></div><div v-if=\"display.type === 'log' && !isInCollapsedGroup(display)\" :class=\"['console-row', 'log-' + display.key, display.groupId ? 'group-header' : '']\" :style=\"{'--depth': display.groupIds.length}\" :title=\"display.name\" @click=\"onLogRowClick(display, $event)\"><span v-if=\"display.count > 1\" class=\"row-icon repeat-count\">{{ display.count }}</span> <span v-else-if=\"display.key === 'error' || display.key === 'assert'\" class=\"row-icon\"><icon name=\"consoleError\"></icon></span><span v-else-if=\"display.key === 'warn'\" class=\"row-icon\"><icon name=\"consoleWarning\"></icon></span><span v-else-if=\"display.key === 'result'\" class=\"row-icon\"><icon name=\"result\"></icon></span><span v-else-if=\"display.groupId\" :class=\"['row-icon', 'arrow', 'expandable', display.isCollapsed ? '' : 'expanded']\"></span><div class=\"row-content\"><table v-if=\"display.table\" class=\"console-table\"><thead><tr><th v-for=\"header in display.table.headers\">{{ header }}</th></tr></thead><tbody><tr v-for=\"row in display.table.rows\"><td>{{ row.index }}</td><td v-for=\"cell in row.cells\"><template v-if=\"cell\"><span v-for=\"part in cell.parts\" :class=\"'t-' + part[0]\">{{ part[1] }}</span></template></td></tr></tbody></table><js-value v-for=\"(description, index) in display.descriptions\" :description=\"description\" :class=\"description.isJoined ? 'is-joined' : ''\" :style=\"description.style\" :key-path=\"[]\" :path=\"[display.logId, index]\"></js-value><div v-if=\"display.stack\" class=\"trace-stack\">{{ display.stack }}</div></div></div><div v-if=\"display.type === 'notice'\" class=\"console-row notice\">{{ display.message }}</div><div v-if=\"display.type === 'error'\" class=\"console-row log-error\"><span v-if=\"display.count > 1\" class=\"row-icon repeat-count\">{{ display.count }}</span> <span v-else class=\"row-icon\"><icon name=\"consoleError\"></icon></span><div class=\"row-content\">{{ display.message }}</div></div></template></div></div><div class=\"divider\" ref=\"mainDivider\"></div><div id=\"editor\"><ace-editor ref=\"editor\" v-model=\"jsCode\" :language=\"language\" :theme=\"theme\" :font-size=\"Math.round(12 * textScale)\" :word-wrap=\"wordWrap\" :rulers=\"rulers\" @key-combo=\"onEditorKeyCombo\" @history-key=\"onEditorHistoryKey\" height=\"100%\"></ace-editor></div></div><div id=\"bottomNav\"><div class=\"brand\"><button class=\"logo-button\" title=\"About YourJS Box\" @click=\"openAbout('about')\"><span class=\"logo\"><svg class=\"logo-mark\"><use href=\"#logo-mark\"/></svg><span class=\"logo-text\"><span class=\"logo-your\">Your</span>JS Box</span></span></button> <span class=\"runner-badge\" :title=\"runnerMode === 'window' ? 'Code runs directly in this page' : 'Code runs in a Web Worker (no DOM access)'\">{{ runnerMode === 'window' ? 'Window' : 'Worker' }}</span></div><div class=\"buttons\"><template v-for=\"bottomButton in bottomButtons\"><span v-if=\"bottomButton.isSeparator\" class=\"separator\"></span> <button v-else :class=\"bottomButton.className\" @click=\"bottomButton.callback.call(this, $event)\" :title=\"bottomButton.title\" :disabled=\"bottomButton.disableIf && bottomButton.disableIf.call(this)\"><icon :name=\"bottomButton.iconName\"></icon><span v-if=\"bottomButton.label\" class=\"label\">{{ bottomButton.label }}</span></button></template></div></div><div v-if=\"isMenuOpen\" class=\"more-menu\" role=\"menu\" aria-label=\"More\" ref=\"moreMenu\" @keydown=\"onMenuKeyDown\"><button class=\"menu-item\" role=\"menuitem\" :disabled=\"!canRunCode\" @click=\"closeMenu(); runAll()\"><span class=\"menu-icon\"><icon name=\"runAll\"></icon></span>Run all blocks<span class=\"menu-shortcut\">{{ modKey === 'Ctrl' ? 'Ctrl+Shift+Enter' : '⇧⌘↵' }}</span></button><div class=\"menu-separator\"></div><div class=\"menu-row\" role=\"group\" aria-label=\"Text size\"><span class=\"menu-row-label\">Text size</span> <button class=\"menu-item menu-step\" role=\"menuitem\" title=\"Smaller text\" aria-label=\"Smaller text\" :disabled=\"!canShrinkText\" @click=\"changeTextScale(-1)\">A−</button> <button class=\"menu-item menu-step menu-percent\" role=\"menuitem\" title=\"Reset the text size\" aria-label=\"Reset the text size\" @click=\"setTextScale(1)\">{{ Math.round(textScale * 100) }}%</button> <button class=\"menu-item menu-step\" role=\"menuitem\" title=\"Bigger text\" aria-label=\"Bigger text\" :disabled=\"!canGrowText\" @click=\"changeTextScale(1)\">A+</button></div><div class=\"menu-separator\"></div><button class=\"menu-item\" role=\"menuitem\" @click=\"openFile()\"><span class=\"menu-icon\"><icon name=\"open\"></icon></span>Open…</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"saveFile()\"><span class=\"menu-icon\"><icon name=\"save\"></icon></span>Save…</button><div class=\"menu-separator\"></div><button v-if=\"isPopOut\" class=\"menu-item\" role=\"menuitem\" @click=\"closeMenu(); bringBack()\"><span class=\"menu-icon\"><icon name=\"popIn\"></icon></span>Bring back into the page</button> <button v-else class=\"menu-item\" role=\"menuitem\" @click=\"popOut()\"><span class=\"menu-icon\"><icon name=\"popOut\"></icon></span>Pop out into a window</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"closeMenu(); openAbout('html')\"><span class=\"menu-icon\"><icon name=\"code\"></icon></span>Copy as HTML…</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"closeMenu(); resetConsole()\"><span class=\"menu-icon\"><icon name=\"refresh\"></icon></span>Reset…</button><div class=\"menu-separator\"></div><button class=\"menu-item\" role=\"menuitem\" @click=\"closeMenu(); openAbout('about')\"><span class=\"menu-icon\"><icon name=\"info\"></icon></span>About YourJS Box</button></div><div v-if=\"isHistoryOpen\" class=\"more-menu history-panel\" role=\"menu\" aria-label=\"History\" ref=\"historyPanel\" @keydown=\"onMenuKeyDown\"><div class=\"history-title\">History</div><div v-if=\"!historyEntries.length\" class=\"history-empty\">Code that you run will show up here.</div><button v-for=\"entry in historyEntries\" class=\"menu-item history-item\" role=\"menuitem\" :title=\"commandHistory[entry.index]\" @click=\"recallHistory(entry.index)\"><span class=\"history-label\">{{ entry.label }}</span> <span class=\"history-detail\">{{ entry.detail }}</span></button><div class=\"history-hint\">Tip: press ↑ at the start of the editor (or ↓ at the end) to go through the history.</div></div><div v-if=\"valueMenu\" class=\"more-menu value-menu\" role=\"menu\" aria-label=\"Value\" ref=\"valueMenu\" :style=\"{left: valueMenu.x + 'px', top: valueMenu.y + 'px'}\" @keydown=\"onMenuKeyDown\"><button class=\"menu-item\" role=\"menuitem\" @click=\"copyValueAsJson()\"><span class=\"menu-icon\"><icon name=\"copy\"></icon></span>Copy as JSON</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"saveValueAsJson()\"><span class=\"menu-icon\"><icon name=\"save\"></icon></span>Save as JSON…</button> <button class=\"menu-item\" role=\"menuitem\" @click=\"storeValueAsGlobal()\"><span class=\"menu-icon\"><icon name=\"variable\"></icon></span>Store as global variable</button> <button v-if=\"valueMenu.propertyPath\" class=\"menu-item\" role=\"menuitem\" @click=\"copyPropertyPath()\"><span class=\"menu-icon\"><icon name=\"path\"></icon></span>Copy property path</button><template v-if=\"!valueMenu.isPrimitive\"><div class=\"menu-separator\"></div><button class=\"menu-item\" role=\"menuitem\" @click=\"refreshValue()\"><span class=\"menu-icon\"><icon name=\"refresh\"></icon></span>Refresh</button></template></div><div v-if=\"toast\" class=\"toast\" role=\"status\">{{ toast.message }}</div><div v-if=\"isPoppedOut\" class=\"popped-out\"><div class=\"popped-out-content\"><span class=\"logo logo-medium\"><svg class=\"logo-mark\"><use href=\"#logo-mark\"/></svg><span class=\"logo-text\"><span class=\"logo-your\">Your</span>JS Box</span></span><div class=\"popped-out-title\">This console is open in a separate window</div><div class=\"popped-out-text\">Its code still runs in this page.</div><div class=\"dialog-buttons\"><button class=\"dialog-button\" @click=\"focusPopOut\">Show the window</button> <button class=\"dialog-button primary\" @click=\"requestPopIn\">Bring it back</button></div></div></div><div v-if=\"isAboutOpen\" class=\"dialog-backdrop\" @mousedown.self=\"closeAbout\" @keydown.esc=\"closeAbout\"><div :class=\"['dialog', 'about-dialog', aboutTab === 'html' ? 'is-export' : '']\" role=\"dialog\" aria-modal=\"true\" aria-label=\"About YourJS Box\"><div class=\"about-header\"><div class=\"tabs\" role=\"tablist\"><button role=\"tab\" :aria-selected=\"aboutTab === 'about'\" :class=\"['tab', aboutTab === 'about' ? 'active' : '']\" @click=\"aboutTab = 'about'\">About</button> <button role=\"tab\" :aria-selected=\"aboutTab === 'html'\" :class=\"['tab', aboutTab === 'html' ? 'active' : '']\" @click=\"aboutTab = 'html'\">Copy as HTML</button></div><button class=\"close-button\" ref=\"aboutCloseButton\" title=\"Close\" @click=\"closeAbout\"><icon name=\"close\"></icon></button></div><div v-if=\"aboutTab === 'about'\" class=\"about-body\"><div class=\"about-title\"><span class=\"logo logo-medium\"><svg class=\"logo-mark\"><use href=\"#logo-mark\"/></svg><span class=\"logo-text\"><span class=\"logo-your\">Your</span>JS Box</span></span><div><div class=\"about-name\">YourJS Box</div><div class=\"about-version\">Version {{ packageInfo.version }}</div></div></div><p class=\"about-description\">An interactive JavaScript console that can be embedded in any web page with a single script tag.</p><div class=\"about-links\"><a :href=\"packageInfo.homepage\" target=\"_blank\" rel=\"noopener\">Website</a> <a :href=\"packageInfo.repoUrl\" target=\"_blank\" rel=\"noopener\">GitHub</a> <a :href=\"packageInfo.repoUrl + '#readme'\" target=\"_blank\" rel=\"noopener\">Documentation</a> <a :href=\"packageInfo.bugsUrl\" target=\"_blank\" rel=\"noopener\">Report an issue</a></div><h3>This console</h3><dl class=\"about-details\"><dt>Code runs in</dt><dd>{{ runnerDescription }}</dd><dt>Theme</dt><dd>{{ themeDescription }}</dd><dt>Language</dt><dd>{{ isTypeScript ? 'TypeScript (types are removed by Babel, not checked)' : 'JavaScript' }}</dd><dt>Code blocks run as</dt><dd>{{ blockTypeDescription }}</dd><dt>Imports</dt><dd>{{ importsUrl ? 'Packages imported by name load from ' + importsUrl.replace('{specifier}', '\u2026') : 'Only URLs and paths can be imported' }}</dd><dt>Results</dt><dd>{{ showResults ? 'The value of the last expression is shown' : 'Not shown' }}</dd><dt>Layout</dt><dd>{{ layoutDescription }}</dd></dl><h3>Keyboard shortcuts</h3><dl class=\"about-details\"><dt><kbd>{{ modKey }}</kbd> + <kbd>Enter</kbd></dt><dd>Run the next block of code</dd><dt><kbd>{{ modKey }}</kbd> + <kbd>Shift</kbd> + <kbd>Enter</kbd></dt><dd>Run all of the remaining blocks</dd><dt><kbd>Ctrl</kbd> + <kbd>L</kbd></dt><dd>Clear the console</dd><dt><kbd>↑</kbd> / <kbd>↓</kbd></dt><dd>Go through the code that was run (at the very start or end of the editor)</dd><dt><kbd>{{ modKey === 'Ctrl' ? 'Alt' : '⌥' }}</kbd> + <kbd>H</kbd></dt><dd>Show the history</dd><dt><kbd>Esc</kbd></dt><dd>Close this window, the menu or full screen</dd></dl><div class=\"about-footer\">MIT License © 2023-present Chris West · Built with {{ builtWith }}</div></div><div v-else class=\"about-body export-body\"><div class=\"export-options\"><div class=\"segmented\" role=\"radiogroup\" aria-label=\"Code\"><button v-for=\"option in exportCodeOptions\" role=\"radio\" :aria-checked=\"exportCode === option.value\" :class=\"exportCode === option.value ? 'active' : ''\" @click=\"exportCode = option.value\">{{ option.label }}</button></div><div class=\"segmented\" role=\"radiogroup\" aria-label=\"Format\"><button v-for=\"option in exportFormatOptions\" role=\"radio\" :aria-checked=\"exportFormat === option.value\" :class=\"exportFormat === option.value ? 'active' : ''\" @click=\"exportFormat = option.value\">{{ option.label }}</button></div></div><div class=\"export-note\">{{ exportNote }}</div><div class=\"export-preview\"><ace-editor :model-value=\"exportHtml\" language=\"html\" :theme=\"theme\" height=\"100%\" :read-only=\"true\"></ace-editor></div><div class=\"export-actions\"><button class=\"dialog-button\" @click=\"downloadExport\">Download page</button> <button class=\"dialog-button primary\" @click=\"copyExport\">{{ copyLabel }}</button></div></div></div></div><div v-if=\"dialog\" class=\"dialog-backdrop\" @mousedown.self=\"closeDialog(false)\" @keydown.esc=\"closeDialog(false)\"><div class=\"dialog\" role=\"alertdialog\" aria-modal=\"true\" :aria-label=\"dialog.title\"><div class=\"dialog-title\">{{ dialog.title }}</div><div class=\"dialog-message\">{{ dialog.message }}</div><div class=\"dialog-buttons\"><button class=\"dialog-button\" @click=\"closeDialog(false)\">Cancel</button> <button class=\"dialog-button primary\" ref=\"dialogConfirmButton\" @click=\"closeDialog(true)\">{{ dialog.confirmText }}</button></div></div></div></div>";
|
|
18
18
|
/**
|
|
19
19
|
* The URL of this script which is used to remove this script's lines from the
|
|
20
20
|
* stack traces of errors in window mode.
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
else if (key === 'assert') {
|
|
196
196
|
if (args[0]) return;
|
|
197
197
|
const rest = args.slice(1);
|
|
198
|
-
|
|
198
|
+
addFormattedLog('assert', !rest.length
|
|
199
199
|
? ['Assertion failed: console.assert']
|
|
200
200
|
: 'string' === typeof rest[0]
|
|
201
201
|
? ['Assertion failed: ' + rest[0], ...rest.slice(1)]
|
|
@@ -225,22 +225,109 @@
|
|
|
225
225
|
else if (key === 'trace') {
|
|
226
226
|
// Removes the first line ("Error") and this runner's own lines.
|
|
227
227
|
const stack = cleanStack(new Error().stack ?? '').split('\n').slice(1).join('\n');
|
|
228
|
-
|
|
228
|
+
addFormattedLog('trace', args.length ? args : ['console.trace'], {stack});
|
|
229
229
|
}
|
|
230
230
|
else if (key === 'group' || key === 'groupCollapsed') {
|
|
231
231
|
const groupId = `${++groupCount}`;
|
|
232
|
-
|
|
232
|
+
addFormattedLog(key, args.length ? args : ['console.group'], {groupId, isCollapsed: key === 'groupCollapsed'});
|
|
233
233
|
groupIds.push(groupId);
|
|
234
234
|
}
|
|
235
235
|
else if (key === 'groupEnd') {
|
|
236
236
|
groupIds.pop();
|
|
237
237
|
}
|
|
238
|
-
else {
|
|
238
|
+
else if (key === 'table' || key === 'dir' || key === 'dirxml') {
|
|
239
239
|
const table = key === 'table' ? parseTable(args[0], args[1]) : null;
|
|
240
240
|
|
|
241
241
|
// console.table() only shows the data that was tabulated.
|
|
242
242
|
addLog(key, table ? args.slice(0, 1) : args, {table});
|
|
243
243
|
}
|
|
244
|
+
else {
|
|
245
|
+
addFormattedLog(key, args);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Like addLog() but if the first argument is a string, the format
|
|
251
|
+
* specifiers in it (eg. "%s" or "%c") are replaced like the browser's
|
|
252
|
+
* console does.
|
|
253
|
+
* @param {string} key
|
|
254
|
+
* @param {any[]} args
|
|
255
|
+
* @param {Object=} extra
|
|
256
|
+
*/
|
|
257
|
+
function addFormattedLog(key, args, extra) {
|
|
258
|
+
const {values, formats} = formatArgs(args);
|
|
259
|
+
addLog(key, values, extra, 0, formats);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Applies the format specifiers in the first argument (if it is a string).
|
|
264
|
+
* The format string is split into pieces of text and values so that values
|
|
265
|
+
* inserted with %o (for example) can still be expanded.
|
|
266
|
+
* @param {any[]} args
|
|
267
|
+
* @returns {{values: any[], formats: {depth?: number, style?: string, isJoined?: boolean}[]}}
|
|
268
|
+
* The values to show and how to show each one: `depth` is used to
|
|
269
|
+
* summarize the value, `style` is CSS from %c and `isJoined` means that no
|
|
270
|
+
* space separates the value from the one before it.
|
|
271
|
+
*/
|
|
272
|
+
function formatArgs(args) {
|
|
273
|
+
const [format, ...rest] = args;
|
|
274
|
+
if ('string' !== typeof format || !format.includes('%')) return {values: args, formats: []};
|
|
275
|
+
|
|
276
|
+
const values = [];
|
|
277
|
+
const formats = [];
|
|
278
|
+
let text = '';
|
|
279
|
+
let style = '';
|
|
280
|
+
const addValue = (value, depth) => {
|
|
281
|
+
if (text) {
|
|
282
|
+
values.push(text);
|
|
283
|
+
formats.push({style, isJoined: values.length > 1});
|
|
284
|
+
text = '';
|
|
285
|
+
}
|
|
286
|
+
if (depth != null) {
|
|
287
|
+
values.push(value);
|
|
288
|
+
formats.push({depth, style, isJoined: values.length > 1});
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
const pattern = /%([sdifoOc%])/g;
|
|
292
|
+
let lastIndex = 0;
|
|
293
|
+
for (let match; (match = pattern.exec(format));) {
|
|
294
|
+
const specifier = match[1];
|
|
295
|
+
text += format.slice(lastIndex, match.index);
|
|
296
|
+
lastIndex = pattern.lastIndex;
|
|
297
|
+
if (specifier === '%') {
|
|
298
|
+
text += '%';
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
// Like the browser, a specifier without a value is left as is.
|
|
302
|
+
if (!rest.length) {
|
|
303
|
+
text += match[0];
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const value = rest.shift();
|
|
307
|
+
if (specifier === 'c') {
|
|
308
|
+
addValue();
|
|
309
|
+
style = `${value}`;
|
|
310
|
+
}
|
|
311
|
+
else if (specifier === 'd' || specifier === 'i') {
|
|
312
|
+
text += 'symbol' === typeof value ? 'NaN' : parseInt(value, 10);
|
|
313
|
+
}
|
|
314
|
+
else if (specifier === 'f') {
|
|
315
|
+
text += 'symbol' === typeof value ? 'NaN' : parseFloat(value);
|
|
316
|
+
}
|
|
317
|
+
else if (specifier === 's' && (value === null || ('object' !== typeof value && 'function' !== typeof value))) {
|
|
318
|
+
text += 'bigint' === typeof value ? `${value}n` : String(value);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
// Objects (and anything for %o or %O) are shown as values that can be
|
|
322
|
+
// expanded. Like the browser, %o shows strings in quotes.
|
|
323
|
+
addValue(value, specifier === 'o' && 'string' === typeof value ? 1 : 0);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
text += format.slice(lastIndex);
|
|
327
|
+
addValue();
|
|
328
|
+
|
|
329
|
+
// The rest of the arguments are shown as usual.
|
|
330
|
+
return {values: [...values, ...rest], formats};
|
|
244
331
|
}
|
|
245
332
|
|
|
246
333
|
/**
|
|
@@ -253,11 +340,13 @@
|
|
|
253
340
|
* @param {number=} depth
|
|
254
341
|
* Optional, defaults to `0`. The depth used to summarize the args (see
|
|
255
342
|
* summarize()).
|
|
343
|
+
* @param {ReturnType<formatArgs>['formats']=} formats
|
|
344
|
+
* Optional. How to show each arg (see formatArgs()).
|
|
256
345
|
*/
|
|
257
|
-
function addLog(key, args, extra, depth = 0) {
|
|
346
|
+
function addLog(key, args, extra, depth = 0, formats = []) {
|
|
258
347
|
// Keep track of the args so that they can be expanded later.
|
|
259
348
|
const logId = `${++logCount}`;
|
|
260
|
-
logArgsById[logId] = args.map(value => ({...summarize(value, depth), value}));
|
|
349
|
+
logArgsById[logId] = args.map((value, index) => ({...summarize(value, formats[index]?.depth ?? depth), value}));
|
|
261
350
|
|
|
262
351
|
send({
|
|
263
352
|
target: 'viewer',
|
|
@@ -265,7 +354,10 @@
|
|
|
265
354
|
args: [{
|
|
266
355
|
logId,
|
|
267
356
|
key,
|
|
268
|
-
descriptions: logArgsById[logId].map(
|
|
357
|
+
descriptions: logArgsById[logId].map((arg, index) => {
|
|
358
|
+
const {style, isJoined} = formats[index] ?? {};
|
|
359
|
+
return {...without(['value'], arg), ...(style ? {style} : {}), ...(isJoined ? {isJoined} : {})};
|
|
360
|
+
}),
|
|
269
361
|
groupIds: groupIds.slice(),
|
|
270
362
|
...extra,
|
|
271
363
|
}]
|
|
@@ -1332,6 +1424,8 @@
|
|
|
1332
1424
|
// The number of groups of code sent to the runner that haven't
|
|
1333
1425
|
// finished running yet.
|
|
1334
1426
|
runningCount: 0,
|
|
1427
|
+
// Set while running all of the remaining blocks (one at a time).
|
|
1428
|
+
isRunningAll: false,
|
|
1335
1429
|
/** @type {{title: string, message: string, confirmText: string, resolve: (isConfirmed: boolean) => void}?} */
|
|
1336
1430
|
dialog: null,
|
|
1337
1431
|
isDisplaysScrolledToBottom: true,
|
|
@@ -1341,6 +1435,12 @@
|
|
|
1341
1435
|
prefersDark: darkSchemeQuery.matches,
|
|
1342
1436
|
runCount: 0,
|
|
1343
1437
|
jsCode: visibleCode,
|
|
1438
|
+
wordWrap: dataset.wordWrap === 'true',
|
|
1439
|
+
rulers: parseRulers(dataset.rulers),
|
|
1440
|
+
// Unless data-hide-empty-output="false" is given, only the editor is
|
|
1441
|
+
// shown until there is something in the output.
|
|
1442
|
+
hideEmptyOutput: dataset.hideEmptyOutput !== 'false',
|
|
1443
|
+
hasShownOutput: false,
|
|
1344
1444
|
// If the orientation wasn't specified it follows the width of the
|
|
1345
1445
|
// console until the user chooses one.
|
|
1346
1446
|
isDividerOrientAuto: !/^(horizontal|vertical)$/.test(dataset.dividerOrient),
|
|
@@ -1484,7 +1584,7 @@
|
|
|
1484
1584
|
/**
|
|
1485
1585
|
* The data attributes for the copied console. data-libraries-url is
|
|
1486
1586
|
* left out because it usually points to files on this site while the
|
|
1487
|
-
* copied console loads
|
|
1587
|
+
* copied console loads YourJS Box from a CDN.
|
|
1488
1588
|
*/
|
|
1489
1589
|
exportDataset() {
|
|
1490
1590
|
const {librariesUrl, ...exportDataset} = dataset;
|
|
@@ -1509,7 +1609,7 @@
|
|
|
1509
1609
|
},
|
|
1510
1610
|
{
|
|
1511
1611
|
iconName: 'clear',
|
|
1512
|
-
title: 'Clear console',
|
|
1612
|
+
title: 'Clear console (Ctrl+L)',
|
|
1513
1613
|
callback() { this.clearConsole(); },
|
|
1514
1614
|
},
|
|
1515
1615
|
{
|
|
@@ -1539,7 +1639,8 @@
|
|
|
1539
1639
|
iconName: this.runningCount ? 'spinner' : 'play',
|
|
1540
1640
|
label: 'Run',
|
|
1541
1641
|
className: 'primary',
|
|
1542
|
-
title: `Run the next block of code (${isMac ? '\u2318' : 'Ctrl+'}Enter)
|
|
1642
|
+
title: `Run the next block of code (${isMac ? '\u2318' : 'Ctrl+'}Enter)`
|
|
1643
|
+
+ ` or all of the blocks (${isMac ? '\u21e7\u2318' : 'Ctrl+Shift+'}Enter)`,
|
|
1543
1644
|
callback() { this.runCode(); },
|
|
1544
1645
|
disableIf() { return !this.canRunCode; }
|
|
1545
1646
|
},
|
|
@@ -1568,10 +1669,14 @@
|
|
|
1568
1669
|
canRunCode() {
|
|
1569
1670
|
return this.jsCode.trim();
|
|
1570
1671
|
},
|
|
1672
|
+
isOutputHidden() {
|
|
1673
|
+
return this.hideEmptyOutput && !this.hasShownOutput && !this.displays.length;
|
|
1674
|
+
},
|
|
1571
1675
|
mainElemClassNames() {
|
|
1572
1676
|
return [
|
|
1573
1677
|
this.dividerOrient === 'vertical' ? 'col-orient' : 'row-orient',
|
|
1574
|
-
this.isMovingDivider ? 'is-moving-divider' : ''
|
|
1678
|
+
this.isMovingDivider ? 'is-moving-divider' : '',
|
|
1679
|
+
this.isOutputHidden ? 'is-output-hidden' : '',
|
|
1575
1680
|
].join(' ');
|
|
1576
1681
|
},
|
|
1577
1682
|
mainElemStyles() {
|
|
@@ -1588,7 +1693,29 @@
|
|
|
1588
1693
|
*/
|
|
1589
1694
|
onEditorKeyCombo(evt) {
|
|
1590
1695
|
if ((evt.ctrlKey || evt.metaKey) && evt.key === 'Enter' && this.canRunCode) {
|
|
1591
|
-
this.
|
|
1696
|
+
if (evt.shiftKey) this.runAll();
|
|
1697
|
+
else this.runCode();
|
|
1698
|
+
}
|
|
1699
|
+
},
|
|
1700
|
+
/**
|
|
1701
|
+
* Runs the remaining blocks one after another. Each block runs once
|
|
1702
|
+
* the one before it (and any hidden blocks after it) finished so that
|
|
1703
|
+
* the output stays in order (see onGroupRan()).
|
|
1704
|
+
*/
|
|
1705
|
+
runAll() {
|
|
1706
|
+
if (!this.canRunCode) return;
|
|
1707
|
+
this.isRunningAll = true;
|
|
1708
|
+
if (!this.runningCount) this.runCode();
|
|
1709
|
+
},
|
|
1710
|
+
/**
|
|
1711
|
+
* Called once the runner finished running a group of code.
|
|
1712
|
+
*/
|
|
1713
|
+
onGroupRan() {
|
|
1714
|
+
this.runningCount = Math.max(0, this.runningCount - 1);
|
|
1715
|
+
this.runHiddenGroups();
|
|
1716
|
+
if (this.isRunningAll && !this.runningCount) {
|
|
1717
|
+
if (this.canRunCode) this.runCode();
|
|
1718
|
+
else this.isRunningAll = false;
|
|
1592
1719
|
}
|
|
1593
1720
|
},
|
|
1594
1721
|
runCode() {
|
|
@@ -1788,6 +1915,8 @@
|
|
|
1788
1915
|
this.hiddenGroups = hiddenGroups.map(group => ({...group}));
|
|
1789
1916
|
this.runCount = 0;
|
|
1790
1917
|
this.runningCount = 0;
|
|
1918
|
+
this.isRunningAll = false;
|
|
1919
|
+
this.hasShownOutput = false;
|
|
1791
1920
|
this.runHistory = [];
|
|
1792
1921
|
this.stopNavigatingHistory();
|
|
1793
1922
|
messageParent({target: 'runner', func: 'reset', args: []});
|
|
@@ -1832,7 +1961,7 @@
|
|
|
1832
1961
|
async saveFile() {
|
|
1833
1962
|
this.closeMenu();
|
|
1834
1963
|
const code = this.currentCode + '\n';
|
|
1835
|
-
const suggestedName = this.fileName || (this.isTypeScript ? '
|
|
1964
|
+
const suggestedName = this.fileName || (this.isTypeScript ? 'yourjs-box-code.ts' : 'yourjs-box-code.js');
|
|
1836
1965
|
const fileType = this.isTypeScript
|
|
1837
1966
|
? {description: 'TypeScript', accept: {'text/typescript': ['.ts', '.mts']}}
|
|
1838
1967
|
: {description: 'JavaScript', accept: {'text/javascript': ['.js', '.mjs']}};
|
|
@@ -1929,7 +2058,7 @@
|
|
|
1929
2058
|
isFullPage: true,
|
|
1930
2059
|
});
|
|
1931
2060
|
const url = URL.createObjectURL(new Blob([html], {type: 'text/html'}));
|
|
1932
|
-
Object.assign(document.createElement('a'), {href: url, download: '
|
|
2061
|
+
Object.assign(document.createElement('a'), {href: url, download: 'yourjs-box.html'}).click();
|
|
1933
2062
|
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
1934
2063
|
},
|
|
1935
2064
|
/**
|
|
@@ -1968,6 +2097,7 @@
|
|
|
1968
2097
|
hiddenGroups: this.hiddenGroups,
|
|
1969
2098
|
runCount: this.runCount,
|
|
1970
2099
|
runningCount: this.runningCount,
|
|
2100
|
+
hasShownOutput: this.hasShownOutput,
|
|
1971
2101
|
dividerOrient: this.dividerOrient,
|
|
1972
2102
|
isDividerOrientAuto: this.isDividerOrientAuto,
|
|
1973
2103
|
dividerPct: this.dividerPct,
|
|
@@ -2110,6 +2240,14 @@
|
|
|
2110
2240
|
this.toggleHistory();
|
|
2111
2241
|
return;
|
|
2112
2242
|
}
|
|
2243
|
+
// Like the browser's console, Ctrl+L clears the console (even on a
|
|
2244
|
+
// Mac).
|
|
2245
|
+
if (evt.ctrlKey && !evt.altKey && !evt.metaKey && !evt.shiftKey && evt.code === 'KeyL') {
|
|
2246
|
+
evt.preventDefault();
|
|
2247
|
+
evt.stopPropagation();
|
|
2248
|
+
this.clearConsole();
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2113
2251
|
if (evt.key === 'Escape' && this.valueMenu) {
|
|
2114
2252
|
this.closeValueMenu();
|
|
2115
2253
|
return;
|
|
@@ -2356,9 +2494,13 @@
|
|
|
2356
2494
|
},
|
|
2357
2495
|
immediate: true,
|
|
2358
2496
|
},
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2497
|
+
'displays.length'(length) {
|
|
2498
|
+
// Once something is in the output it stays shown (even after Clear)
|
|
2499
|
+
// until the console is reset.
|
|
2500
|
+
if (length) this.hasShownOutput = true;
|
|
2501
|
+
|
|
2502
|
+
// Like the browser's console, keep showing the newest output unless
|
|
2503
|
+
// the user has scrolled up to look at older output.
|
|
2362
2504
|
if (this.isDisplaysScrolledToBottom) {
|
|
2363
2505
|
this.$nextTick(() => {
|
|
2364
2506
|
const scroller = this.$refs.displaysScroller;
|
|
@@ -2416,8 +2558,11 @@
|
|
|
2416
2558
|
'language',
|
|
2417
2559
|
'modelValue',
|
|
2418
2560
|
'readOnly',
|
|
2561
|
+
// The columns (eg. [80, 120]) where lines are shown in the editor.
|
|
2562
|
+
'rulers',
|
|
2419
2563
|
'theme',
|
|
2420
2564
|
'width',
|
|
2565
|
+
'wordWrap',
|
|
2421
2566
|
],
|
|
2422
2567
|
computed: {
|
|
2423
2568
|
infoNotes() {
|
|
@@ -2461,6 +2606,12 @@
|
|
|
2461
2606
|
fontSize(newValue) {
|
|
2462
2607
|
if (newValue) this.editor.setFontSize(newValue);
|
|
2463
2608
|
},
|
|
2609
|
+
wordWrap(newValue) {
|
|
2610
|
+
this.editor.session.setUseWrapMode(!!newValue);
|
|
2611
|
+
},
|
|
2612
|
+
rulers() {
|
|
2613
|
+
this.updateRulers();
|
|
2614
|
+
},
|
|
2464
2615
|
modelValue(newValue) {
|
|
2465
2616
|
if (newValue !== this.editor.getValue()) {
|
|
2466
2617
|
// -1 puts the cursor at the start instead of selecting everything.
|
|
@@ -2480,6 +2631,18 @@
|
|
|
2480
2631
|
// Read-only editors don't need syntax checking (eg. hints in the gutter).
|
|
2481
2632
|
if (this.fontSize) editor.setFontSize(this.fontSize);
|
|
2482
2633
|
|
|
2634
|
+
editor.session.setUseWrapMode(!!this.wordWrap);
|
|
2635
|
+
|
|
2636
|
+
// Ace only has one ruler (its print margin) so when rulers are given
|
|
2637
|
+
// they are drawn instead, using the print margin's class so that each
|
|
2638
|
+
// theme colors them.
|
|
2639
|
+
if (this.rulers) {
|
|
2640
|
+
editor.setShowPrintMargin(false);
|
|
2641
|
+
this.rulersLayer = Object.assign(document.createElement('div'), {className: 'ace_layer ace_print-margin-layer'});
|
|
2642
|
+
editor.renderer.content.prepend(this.rulersLayer);
|
|
2643
|
+
editor.renderer.on('afterRender', () => this.updateRulers());
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2483
2646
|
if (this.readOnly) {
|
|
2484
2647
|
editor.setReadOnly(true);
|
|
2485
2648
|
editor.setHighlightActiveLine(false);
|
|
@@ -2558,6 +2721,24 @@
|
|
|
2558
2721
|
if (isCombo) this.$emit('keyCombo', evt);
|
|
2559
2722
|
});
|
|
2560
2723
|
},
|
|
2724
|
+
methods: {
|
|
2725
|
+
/**
|
|
2726
|
+
* Puts a line at each ruler's column (which depends on the font size).
|
|
2727
|
+
*/
|
|
2728
|
+
updateRulers() {
|
|
2729
|
+
const {rulersLayer, editor} = this;
|
|
2730
|
+
if (!rulersLayer) return;
|
|
2731
|
+
const {characterWidth, $padding: padding} = editor.renderer;
|
|
2732
|
+
const columns = Array.from(this.rulers ?? []);
|
|
2733
|
+
while (rulersLayer.children.length > columns.length) rulersLayer.lastChild.remove();
|
|
2734
|
+
while (rulersLayer.children.length < columns.length) {
|
|
2735
|
+
rulersLayer.append(Object.assign(document.createElement('div'), {className: 'ace_print-margin'}));
|
|
2736
|
+
}
|
|
2737
|
+
columns.forEach((column, index) => {
|
|
2738
|
+
rulersLayer.children[index].style.left = `${Math.round(characterWidth * column + padding)}px`;
|
|
2739
|
+
});
|
|
2740
|
+
},
|
|
2741
|
+
},
|
|
2561
2742
|
template: '<div ref="editor" :style="style"></div>',
|
|
2562
2743
|
};
|
|
2563
2744
|
}
|
|
@@ -2638,6 +2819,7 @@
|
|
|
2638
2819
|
horizontalView: '<svg viewBox="0 0 15 15"><path fill="currentColor" fill-rule="evenodd" d="M1.5 2h12a.5.5 0 0 1 .5.5V7H1V2.5a.5.5 0 0 1 .5-.5M1 8v4.5a.5.5 0 0 0 .5.5h12a.5.5 0 0 0 .5-.5V8zM0 2.5A1.5 1.5 0 0 1 1.5 1h12A1.5 1.5 0 0 1 15 2.5v10a1.5 1.5 0 0 1-1.5 1.5h-12A1.5 1.5 0 0 1 0 12.5z" clip-rule="evenodd"/></svg>',
|
|
2639
2820
|
// https://icon-sets.iconify.design/radix-icons/view-vertical/
|
|
2640
2821
|
verticalView: '<svg viewBox="0 0 15 15"><path fill="currentColor" fill-rule="evenodd" d="M8 2h5.5a.5.5 0 0 1 .5.5v10a.5.5 0 0 1-.5.5H8zM7 2H1.5a.5.5 0 0 0-.5.5v10a.5.5 0 0 0 .5.5H7zm-7 .5A1.5 1.5 0 0 1 1.5 1h12A1.5 1.5 0 0 1 15 2.5v10a1.5 1.5 0 0 1-1.5 1.5h-12A1.5 1.5 0 0 1 0 12.5z" clip-rule="evenodd"/></svg>',
|
|
2822
|
+
runAll: '<svg viewBox="0 0 16 16"><path fill="currentColor" d="M1 2.5v11L7.5 8zM8 2.5v11L14.5 8z"/></svg>',
|
|
2641
2823
|
// https://icon-sets.iconify.design/vaadin/play/
|
|
2642
2824
|
play: '<svg viewBox="0 0 16 16"><path fill="currentColor" d="M2 1v14l12-7z"/></svg>',
|
|
2643
2825
|
// https://icon-sets.iconify.design/mdi/error-outline/
|
|
@@ -3116,7 +3298,7 @@
|
|
|
3116
3298
|
' <head>',
|
|
3117
3299
|
' <meta charset="utf-8">',
|
|
3118
3300
|
' <meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
3119
|
-
' <title>
|
|
3301
|
+
' <title>YourJS Box</title>',
|
|
3120
3302
|
' <style>',
|
|
3121
3303
|
' html, body { height: 100%; margin: 0; }',
|
|
3122
3304
|
' </style>',
|
|
@@ -3226,11 +3408,16 @@
|
|
|
3226
3408
|
*/
|
|
3227
3409
|
function appendLog({logId, key, descriptions, table, groupIds, groupId, isCollapsed, stack}) {
|
|
3228
3410
|
key = `${key}`;
|
|
3229
|
-
|
|
3411
|
+
const display = {
|
|
3230
3412
|
type: 'log',
|
|
3231
3413
|
key,
|
|
3232
3414
|
name: key === 'result' ? 'Result' : `console.${key}`,
|
|
3233
|
-
descriptions: Array.from(descriptions,
|
|
3415
|
+
descriptions: Array.from(descriptions, description => ({
|
|
3416
|
+
...sanitizeDescription(description),
|
|
3417
|
+
// From format specifiers (see formatArgs() in the runner).
|
|
3418
|
+
style: sanitizeLogStyle(Object(description).style),
|
|
3419
|
+
isJoined: !!Object(description).isJoined,
|
|
3420
|
+
})),
|
|
3234
3421
|
table: table ? {
|
|
3235
3422
|
headers: Array.from(table.headers, h => `${h}`),
|
|
3236
3423
|
rows: Array.from(table.rows, row => ({
|
|
@@ -3245,7 +3432,43 @@
|
|
|
3245
3432
|
isCollapsed: !!isCollapsed,
|
|
3246
3433
|
stack: stack != null ? `${stack}` : null,
|
|
3247
3434
|
logId: `${logId}`,
|
|
3248
|
-
|
|
3435
|
+
// How many times in a row this message was logged.
|
|
3436
|
+
count: 1,
|
|
3437
|
+
};
|
|
3438
|
+
|
|
3439
|
+
// Like the browser, a message that is the same as the one before it is
|
|
3440
|
+
// shown once with a count. Only messages made up of primitives are
|
|
3441
|
+
// compared because objects may have changed in between.
|
|
3442
|
+
const lastDisplay = mountedApp.displays[mountedApp.displays.length - 1];
|
|
3443
|
+
const getRepeatKey = d => !d.table && !d.groupId && !d.stack && d.descriptions.every(desc => desc.isPrimitive)
|
|
3444
|
+
? JSON.stringify([d.key, d.groupIds, d.descriptions])
|
|
3445
|
+
: null;
|
|
3446
|
+
const repeatKey = getRepeatKey(display);
|
|
3447
|
+
if (repeatKey && lastDisplay?.type === 'log' && getRepeatKey(lastDisplay) === repeatKey) {
|
|
3448
|
+
lastDisplay.count = (lastDisplay.count || 1) + 1;
|
|
3449
|
+
}
|
|
3450
|
+
else {
|
|
3451
|
+
mountedApp.displays.push(display);
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
/**
|
|
3456
|
+
* Only allows the CSS properties (from %c) that the browser's console allows
|
|
3457
|
+
* and never anything that loads a URL.
|
|
3458
|
+
* @param {*} style
|
|
3459
|
+
* @returns {string}
|
|
3460
|
+
*/
|
|
3461
|
+
function sanitizeLogStyle(style) {
|
|
3462
|
+
if ('string' !== typeof style || !style.trim()) return '';
|
|
3463
|
+
const elem = document.createElement('span');
|
|
3464
|
+
elem.style.cssText = style;
|
|
3465
|
+
const allowed = /^(-webkit-)?(background|border|box-decoration-break|box-shadow|clear|color|cursor|display|float|font|letter-spacing|line-height|margin|outline|padding|text|white-space|word|writing-mode)(-|$)/;
|
|
3466
|
+
return Array.from(elem.style)
|
|
3467
|
+
.filter(name => allowed.test(name))
|
|
3468
|
+
.map(name => [name, elem.style.getPropertyValue(name)])
|
|
3469
|
+
.filter(([, value]) => !/\b(url|image|image-set|cross-fade|element)\s*\(/i.test(value))
|
|
3470
|
+
.map(([name, value]) => `${name}: ${value}`)
|
|
3471
|
+
.join('; ');
|
|
3249
3472
|
}
|
|
3250
3473
|
|
|
3251
3474
|
/**
|
|
@@ -3256,11 +3479,18 @@
|
|
|
3256
3479
|
* @param {number=} options.column
|
|
3257
3480
|
*/
|
|
3258
3481
|
function appendError({message, line, column}) {
|
|
3482
|
+
message = `${message}`;
|
|
3483
|
+
const lastDisplay = mountedApp.displays[mountedApp.displays.length - 1];
|
|
3484
|
+
if (lastDisplay?.type === 'error' && lastDisplay.message === message) {
|
|
3485
|
+
lastDisplay.count = (lastDisplay.count || 1) + 1;
|
|
3486
|
+
return;
|
|
3487
|
+
}
|
|
3259
3488
|
mountedApp.displays.push({
|
|
3260
3489
|
type: 'error',
|
|
3261
|
-
message
|
|
3490
|
+
message,
|
|
3262
3491
|
line: +line,
|
|
3263
3492
|
column: +column,
|
|
3493
|
+
count: 1,
|
|
3264
3494
|
});
|
|
3265
3495
|
}
|
|
3266
3496
|
|
|
@@ -3330,8 +3560,7 @@
|
|
|
3330
3560
|
* NOTE: Called via main by the runner once it has run a group of code.
|
|
3331
3561
|
*/
|
|
3332
3562
|
function onCodeRan() {
|
|
3333
|
-
mountedApp.
|
|
3334
|
-
mountedApp.runHiddenGroups();
|
|
3563
|
+
mountedApp.onGroupRan();
|
|
3335
3564
|
}
|
|
3336
3565
|
|
|
3337
3566
|
/**
|
|
@@ -3371,6 +3600,20 @@
|
|
|
3371
3600
|
mountedApp.displays = [{type: 'notice', message: 'Console was cleared'}];
|
|
3372
3601
|
}
|
|
3373
3602
|
|
|
3603
|
+
/**
|
|
3604
|
+
* Turns data-rulers (eg. "80, 120") into the columns for the rulers. Like
|
|
3605
|
+
* Ace's print margin, there is a ruler at column 80 by default and "" (or
|
|
3606
|
+
* "none") turns them off.
|
|
3607
|
+
* @param {string=} rulers
|
|
3608
|
+
* @returns {number[]}
|
|
3609
|
+
*/
|
|
3610
|
+
function parseRulers(rulers) {
|
|
3611
|
+
return `${rulers ?? '80'}`
|
|
3612
|
+
.split(/[\s,]+/)
|
|
3613
|
+
.map(Number)
|
|
3614
|
+
.filter(column => Number.isInteger(column) && column > 0);
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3374
3617
|
/**
|
|
3375
3618
|
* Only allows non-negative integers to be used as an index.
|
|
3376
3619
|
* @param {*} value
|
|
@@ -3386,7 +3629,7 @@
|
|
|
3386
3629
|
// load other files (eg. language modes) from next to these files.
|
|
3387
3630
|
jsUrls: [
|
|
3388
3631
|
libraryUrl('vue', 'dist/vue.global.prod.js'),
|
|
3389
|
-
libraryUrl('ace-builds', 'src-noconflict/ace.js'),
|
|
3632
|
+
libraryUrl('ace-builds', 'src-min-noconflict/ace.js'),
|
|
3390
3633
|
libraryUrl('prismjs', 'components/prism-core.min.js'),
|
|
3391
3634
|
libraryUrl('prismjs', 'plugins/autoloader/prism-autoloader.min.js'),
|
|
3392
3635
|
libraryUrl('prismjs', 'plugins/match-braces/prism-match-braces.min.js'),
|
|
@@ -3396,12 +3639,14 @@
|
|
|
3396
3639
|
// Removes the types from TypeScript code before it is run.
|
|
3397
3640
|
...language === 'typescript' ? [libraryUrl('@babel/standalone', 'babel.min.js')] : [],
|
|
3398
3641
|
],
|
|
3399
|
-
cssUrls: [
|
|
3400
|
-
|
|
3642
|
+
cssUrls: ['data:text/css,' + encodeURIComponent(VIEWER_IFRAME_CSS)],
|
|
3643
|
+
// These come from the CDN so they are loaded after the loading screen
|
|
3644
|
+
// (instead of in the head) so that it can be shown while they load.
|
|
3645
|
+
bodyCssUrls: [
|
|
3401
3646
|
libraryUrl('prism-themes', 'themes/prism-vsc-dark-plus.min.css'),
|
|
3402
3647
|
libraryUrl('prismjs', 'plugins/match-braces/prism-match-braces.min.css'),
|
|
3403
3648
|
],
|
|
3404
|
-
head: targetWindow ? '<meta charset="utf-8"><title>
|
|
3649
|
+
head: targetWindow ? '<meta charset="utf-8"><title>YourJS Box</title>' : '',
|
|
3405
3650
|
htmlAttributes: 'data-theme="' + theme + '"',
|
|
3406
3651
|
targetWindow,
|
|
3407
3652
|
onMessage(message) {
|
|
@@ -3522,7 +3767,7 @@
|
|
|
3522
3767
|
* The options of YourJSBox.create() which are the same as the data
|
|
3523
3768
|
* attributes of a script tag.
|
|
3524
3769
|
*/
|
|
3525
|
-
const CONSOLE_OPTION_NAMES = ['blockType', 'dividerOrient', 'hidePrefix', 'importsUrl', 'language', 'librariesUrl', 'loading', 'runner', 'showResults', 'theme'];
|
|
3770
|
+
const CONSOLE_OPTION_NAMES = ['blockType', 'dividerOrient', 'hideEmptyOutput', 'hidePrefix', 'importsUrl', 'language', 'librariesUrl', 'loading', 'rulers', 'runner', 'showResults', 'theme', 'wordWrap'];
|
|
3526
3771
|
|
|
3527
3772
|
/**
|
|
3528
3773
|
* The JavaScript API for creating consoles (available as window.YourJSBox).
|
|
@@ -3556,6 +3801,9 @@
|
|
|
3556
3801
|
* @param {string=} options.librariesUrl
|
|
3557
3802
|
* @param {string=} options.importsUrl
|
|
3558
3803
|
* @param {string=} options.loading
|
|
3804
|
+
* @param {(string|number|number[])=} options.rulers
|
|
3805
|
+
* @param {(boolean|string)=} options.wordWrap
|
|
3806
|
+
* @param {(boolean|string)=} options.hideEmptyOutput
|
|
3559
3807
|
* The same as the data attributes of a script tag.
|
|
3560
3808
|
* @returns {{element: HTMLIFrameElement, destroy: () => void}}
|
|
3561
3809
|
*/
|
|
@@ -3586,6 +3834,75 @@
|
|
|
3586
3834
|
insert: element => PLACEMENTS[placement](targetElement, element),
|
|
3587
3835
|
});
|
|
3588
3836
|
},
|
|
3837
|
+
|
|
3838
|
+
/**
|
|
3839
|
+
* Creates a console from each of the elements (eg. code blocks) using the
|
|
3840
|
+
* element's text as the code that the console starts with. A `<code>`
|
|
3841
|
+
* that is the only thing in a `<pre>` (eg. a Markdown code block) is
|
|
3842
|
+
* treated as the `<pre>`.
|
|
3843
|
+
* @param {string|Element|ArrayLike<Element>|Iterable<Element>} elements
|
|
3844
|
+
* A CSS selector for the elements, an element or a list of elements (eg.
|
|
3845
|
+
* an array or a NodeList).
|
|
3846
|
+
* @param {Object=} options
|
|
3847
|
+
* Optional. The same options as create() (except `target` and `code`)
|
|
3848
|
+
* for every console. `placement` defaults to `"replace"` and `height`
|
|
3849
|
+
* defaults to a height that fits the code (from 250px to 600px). The
|
|
3850
|
+
* `data-*` attributes of each element (eg. `data-theme="dark"`) override
|
|
3851
|
+
* these options, and a `language-ts` or `language-typescript` class
|
|
3852
|
+
* turns on TypeScript.
|
|
3853
|
+
* @returns {ReturnType<YourJSBox['create']>[]}
|
|
3854
|
+
* The consoles in the same order as the elements. Destroying a console
|
|
3855
|
+
* that replaced its element puts the element back.
|
|
3856
|
+
*/
|
|
3857
|
+
from(elements, options) {
|
|
3858
|
+
options = Object(options);
|
|
3859
|
+
const list = 'string' === typeof elements
|
|
3860
|
+
? document.querySelectorAll(elements)
|
|
3861
|
+
: elements?.nodeType === 1 ? [elements] : elements;
|
|
3862
|
+
if (list == null || 'string' === typeof list || !(Symbol.iterator in Object(list) || 'number' === typeof list.length)) {
|
|
3863
|
+
throw new TypeError('YourJSBox.from(): elements must be a CSS selector, an element or a list of elements.');
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
return Array.from(list, source => {
|
|
3867
|
+
if (source?.nodeType !== 1) {
|
|
3868
|
+
throw new TypeError('YourJSBox.from(): every item in the list must be an element.');
|
|
3869
|
+
}
|
|
3870
|
+
const pre = source.parentElement;
|
|
3871
|
+
const target = source.localName === 'code' && pre?.localName === 'pre' && pre.children.length === 1
|
|
3872
|
+
? pre
|
|
3873
|
+
: source;
|
|
3874
|
+
const code = source.textContent;
|
|
3875
|
+
|
|
3876
|
+
// The options for every console, then what the element says.
|
|
3877
|
+
const elementOptions = {};
|
|
3878
|
+
if (/(^|\s)lang(uage)?-(ts|typescript)(\s|$)/i.test(`${target.className} ${source.className}`)) {
|
|
3879
|
+
elementOptions.language = 'typescript';
|
|
3880
|
+
}
|
|
3881
|
+
for (const name of [...CONSOLE_OPTION_NAMES, 'placement', 'height']) {
|
|
3882
|
+
const value = source.dataset[name] ?? target.dataset[name];
|
|
3883
|
+
// Like the option, a height that is just a number is in pixels.
|
|
3884
|
+
if (value != null) elementOptions[name] = name === 'height' && /^\d+(\.\d+)?$/.test(value) ? +value : value;
|
|
3885
|
+
}
|
|
3886
|
+
const lineCount = code.trim().split('\n').length;
|
|
3887
|
+
const box = YourJSBox.create({
|
|
3888
|
+
height: Math.min(600, Math.max(250, 200 + lineCount * 18)),
|
|
3889
|
+
placement: 'replace',
|
|
3890
|
+
...options,
|
|
3891
|
+
...elementOptions,
|
|
3892
|
+
target,
|
|
3893
|
+
code,
|
|
3894
|
+
});
|
|
3895
|
+
|
|
3896
|
+
if ((elementOptions.placement ?? options.placement ?? 'replace') !== 'replace') return box;
|
|
3897
|
+
return {
|
|
3898
|
+
element: box.element,
|
|
3899
|
+
destroy() {
|
|
3900
|
+
if (box.element.isConnected) box.element.replaceWith(target);
|
|
3901
|
+
box.destroy();
|
|
3902
|
+
},
|
|
3903
|
+
};
|
|
3904
|
+
});
|
|
3905
|
+
},
|
|
3589
3906
|
});
|
|
3590
3907
|
|
|
3591
3908
|
// NOTE: This solution was intentionally written without using newer JS
|
|
@@ -3631,6 +3948,9 @@
|
|
|
3631
3948
|
* parent level.
|
|
3632
3949
|
* @param {string[]=} options.jsUrls
|
|
3633
3950
|
* @param {string[]=} options.cssUrls
|
|
3951
|
+
* @param {string[]=} options.bodyCssUrls
|
|
3952
|
+
* Stylesheets that are loaded after `options.body` (so that it can be
|
|
3953
|
+
* shown while they load).
|
|
3634
3954
|
* @param {string=} options.head
|
|
3635
3955
|
* @param {string=} options.body
|
|
3636
3956
|
* @param {(CSSStyleDeclaration|string)=} options.style
|
|
@@ -3707,6 +4027,9 @@
|
|
|
3707
4027
|
'</head>',
|
|
3708
4028
|
'<body>',
|
|
3709
4029
|
options.body || '',
|
|
4030
|
+
(options.bodyCssUrls || []).map(function(cssUrl) {
|
|
4031
|
+
return '<link href="' + cssUrl + '" rel="stylesheet">';
|
|
4032
|
+
}).join('\n'),
|
|
3710
4033
|
// The scripts are loaded after the body so that the body can be shown
|
|
3711
4034
|
// while they load.
|
|
3712
4035
|
(options.jsUrls || []).map(function(jsUrl) {
|