yourjs-box 1.1.2 → 1.2.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 +63 -0
- package/dist/yourjs-box.full.js +361 -66
- package/dist/yourjs-box.js +57 -14
- package/dist/yourjs-box.min.js +5 -4
- package/package.json +3 -1
package/dist/yourjs-box.full.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
/*! yourjs-box v1.
|
|
1
|
+
/*! yourjs-box v1.2.0 | (c) 2023-present Christopher 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}@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:12px;line-height:16px}.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)}.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:12px!important;line-height:16px!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}#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}@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:12px;line-height:16px}.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:12px!important;line-height:16px!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}";
|
|
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.2.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></div><div id=\"vueApp\" v-cloak><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=\"javascript\" :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'\" :class=\"['console-row', 'log-' + display.key]\" :title=\"display.name\"><span v-if=\"display.key === 'error'\" class=\"row-icon\"><icon name=\"consoleError\"></icon></span><span v-else-if=\"display.key === 'warn'\" class=\"row-icon\"><icon name=\"consoleWarning\"></icon></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\" :path=\"[display.logId, index]\"></js-value></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 v-model=\"jsCode\" language=\"javascript\" :theme=\"theme\" @key-combo=\"onEditorKeyCombo\" 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=\"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>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>Esc</kbd></dt><dd>Close this window</dd></dl><div class=\"about-footer\">MIT License © 2023-present Christopher West · Built with Vue, Ace and
|
|
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><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=\"javascript\" :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\" :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 v-model=\"jsCode\" language=\"javascript\" :theme=\"theme\" @key-combo=\"onEditorKeyCombo\" 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=\"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>Code blocks run as</dt><dd>{{ blockTypeDescription }}</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>Esc</kbd></dt><dd>Close this window</dd></dl><div class=\"about-footer\">MIT License © 2023-present Christopher West · Built with Vue {{ libraryVersions.vue }}, Ace {{ libraryVersions['ace-builds'] }}, Prism {{ libraryVersions.prismjs }} and Acorn {{ libraryVersions.acorn }}</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.
|
|
@@ -28,6 +28,40 @@
|
|
|
28
28
|
*/
|
|
29
29
|
const ORIGINAL_CONSOLE = window[Symbol.for('yourjs-box.originalConsole')] ??= {...console};
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* The libraries that the viewer loads along with the exact versions that it
|
|
33
|
+
* was tested with.
|
|
34
|
+
*/
|
|
35
|
+
const LIBRARY_VERSIONS = {
|
|
36
|
+
'ace-builds': '1.44.0',
|
|
37
|
+
'acorn': '8.18.0',
|
|
38
|
+
'prism-themes': '1.9.0',
|
|
39
|
+
'prismjs': '1.30.0',
|
|
40
|
+
'vue': '3.5.43',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Where the libraries are loaded from unless data-libraries-url is given.
|
|
45
|
+
* `{name}` and `{version}` are replaced with each library's name and version.
|
|
46
|
+
*/
|
|
47
|
+
const DEFAULT_LIBRARIES_URL = 'https://unpkg.com/{name}@{version}/';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} librariesUrl
|
|
51
|
+
* The URL template (see DEFAULT_LIBRARIES_URL). Relative URLs are relative
|
|
52
|
+
* to the page.
|
|
53
|
+
* @param {keyof LIBRARY_VERSIONS} name
|
|
54
|
+
* @param {string} path
|
|
55
|
+
* The path of the file within the library's package.
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function getLibraryFileUrl(librariesUrl, name, path) {
|
|
59
|
+
const baseUrl = librariesUrl
|
|
60
|
+
.replace(/\{(name|version)\}/g, (_, key) => key === 'name' ? name : LIBRARY_VERSIONS[name])
|
|
61
|
+
.replace(/\/?$/, '/');
|
|
62
|
+
return new URL(baseUrl + path, document.baseURI).href;
|
|
63
|
+
}
|
|
64
|
+
|
|
31
65
|
/**
|
|
32
66
|
* The only functions that may be relayed to the viewer and to the runner.
|
|
33
67
|
* The runner executes the user's code so anything it sends must be limited
|
|
@@ -76,42 +110,143 @@
|
|
|
76
110
|
let logCount = 0;
|
|
77
111
|
let snippetCount = 0;
|
|
78
112
|
let canImportDataUrls = true;
|
|
113
|
+
// The type of the block of code that is running (see runCode()).
|
|
114
|
+
let runningBlockType;
|
|
115
|
+
// How wrapping the last expression of each block shifted the columns on its
|
|
116
|
+
// line so that stack traces can show the original columns.
|
|
117
|
+
const columnShiftsBySnippet = {};
|
|
118
|
+
|
|
119
|
+
const counts = {};
|
|
120
|
+
const timers = {};
|
|
121
|
+
// The IDs of the groups (from console.group()) that are currently open.
|
|
122
|
+
let groupIds = [];
|
|
123
|
+
let groupCount = 0;
|
|
124
|
+
|
|
125
|
+
// Called by the code that is run (see runCode()) with the value of the last
|
|
126
|
+
// expression. The key is unique to this runner because in window mode more
|
|
127
|
+
// than one console can be on the same page.
|
|
128
|
+
const RESULT_KEY = `yourjs-box.result.${Math.random().toString(36).slice(2)}`;
|
|
129
|
+
globalThis[Symbol.for(RESULT_KEY)] = reportResult;
|
|
79
130
|
|
|
80
131
|
// Overrides for console functions. In window mode this also captures
|
|
81
132
|
// anything else that the page logs, just like the browser's console.
|
|
82
|
-
for (const key of [
|
|
133
|
+
for (const key of [
|
|
134
|
+
'assert', 'clear', 'count', 'countReset', 'debug', 'dir', 'dirxml', 'error',
|
|
135
|
+
'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'table', 'time',
|
|
136
|
+
'timeEnd', 'timeLog', 'trace', 'warn',
|
|
137
|
+
]) {
|
|
83
138
|
const original = console[key];
|
|
84
139
|
if ('function' !== typeof original) continue;
|
|
85
140
|
console[key] = function(...args) {
|
|
86
|
-
|
|
87
|
-
clearLogs();
|
|
88
|
-
send({target: 'viewer', func: 'clearDisplays', args: [true]});
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
const table = key === 'table' ? parseTable(args[0], args[1]) : null;
|
|
141
|
+
handleConsoleCall(key, args);
|
|
92
142
|
|
|
93
|
-
|
|
94
|
-
|
|
143
|
+
// Calls and returns the original console function.
|
|
144
|
+
return original.apply(this, arguments);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
95
147
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Shows what was passed to a console function in the same way that the
|
|
150
|
+
* browser's console would.
|
|
151
|
+
* @param {string} key
|
|
152
|
+
* @param {any[]} args
|
|
153
|
+
*/
|
|
154
|
+
function handleConsoleCall(key, args) {
|
|
155
|
+
const label = args.length && args[0] !== undefined ? `${args[0]}` : 'default';
|
|
99
156
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
157
|
+
if (key === 'clear') {
|
|
158
|
+
clearLogs();
|
|
159
|
+
groupIds = [];
|
|
160
|
+
send({target: 'viewer', func: 'clearDisplays', args: [true]});
|
|
161
|
+
}
|
|
162
|
+
else if (key === 'assert') {
|
|
163
|
+
if (args[0]) return;
|
|
164
|
+
const rest = args.slice(1);
|
|
165
|
+
addLog('assert', !rest.length
|
|
166
|
+
? ['Assertion failed: console.assert']
|
|
167
|
+
: 'string' === typeof rest[0]
|
|
168
|
+
? ['Assertion failed: ' + rest[0], ...rest.slice(1)]
|
|
169
|
+
: ['Assertion failed:', ...rest]);
|
|
170
|
+
}
|
|
171
|
+
else if (key === 'count') {
|
|
172
|
+
counts[label] = (counts[label] || 0) + 1;
|
|
173
|
+
addLog('count', [`${label}: ${counts[label]}`]);
|
|
174
|
+
}
|
|
175
|
+
else if (key === 'countReset') {
|
|
176
|
+
if (Object.hasOwn(counts, label)) counts[label] = 0;
|
|
177
|
+
else addLog('warn', [`Count for '${label}' does not exist`]);
|
|
178
|
+
}
|
|
179
|
+
else if (key === 'time') {
|
|
180
|
+
if (Object.hasOwn(timers, label)) addLog('warn', [`Timer '${label}' already exists`]);
|
|
181
|
+
else timers[label] = performance.now();
|
|
182
|
+
}
|
|
183
|
+
else if (key === 'timeLog' || key === 'timeEnd') {
|
|
184
|
+
if (!Object.hasOwn(timers, label)) {
|
|
185
|
+
addLog('warn', [`Timer '${label}' does not exist`]);
|
|
186
|
+
return;
|
|
110
187
|
}
|
|
188
|
+
const ms = performance.now() - timers[label];
|
|
189
|
+
if (key === 'timeEnd') delete timers[label];
|
|
190
|
+
addLog(key, [`${label}: ${+ms.toFixed(3)} ms`, ...(key === 'timeLog' ? args.slice(1) : [])]);
|
|
191
|
+
}
|
|
192
|
+
else if (key === 'trace') {
|
|
193
|
+
// Removes the first line ("Error") and this runner's own lines.
|
|
194
|
+
const stack = cleanStack(new Error().stack ?? '').split('\n').slice(1).join('\n');
|
|
195
|
+
addLog('trace', args.length ? args : ['console.trace'], {stack});
|
|
196
|
+
}
|
|
197
|
+
else if (key === 'group' || key === 'groupCollapsed') {
|
|
198
|
+
const groupId = `${++groupCount}`;
|
|
199
|
+
addLog(key, args.length ? args : ['console.group'], {groupId, isCollapsed: key === 'groupCollapsed'});
|
|
200
|
+
groupIds.push(groupId);
|
|
201
|
+
}
|
|
202
|
+
else if (key === 'groupEnd') {
|
|
203
|
+
groupIds.pop();
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const table = key === 'table' ? parseTable(args[0], args[1]) : null;
|
|
111
207
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
}
|
|
208
|
+
// console.table() only shows the data that was tabulated.
|
|
209
|
+
addLog(key, table ? args.slice(0, 1) : args, {table});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Sends a message to show in the console.
|
|
215
|
+
* @param {string} key
|
|
216
|
+
* The name of the console function (eg. "log").
|
|
217
|
+
* @param {any[]} args
|
|
218
|
+
* @param {Object=} extra
|
|
219
|
+
* Other properties to send along with the message.
|
|
220
|
+
* @param {number=} depth
|
|
221
|
+
* Optional, defaults to `0`. The depth used to summarize the args (see
|
|
222
|
+
* summarize()).
|
|
223
|
+
*/
|
|
224
|
+
function addLog(key, args, extra, depth = 0) {
|
|
225
|
+
// Keep track of the args so that they can be expanded later.
|
|
226
|
+
const logId = `${++logCount}`;
|
|
227
|
+
logArgsById[logId] = args.map(value => ({...summarize(value, depth), value}));
|
|
228
|
+
|
|
229
|
+
send({
|
|
230
|
+
target: 'viewer',
|
|
231
|
+
func: 'appendLog',
|
|
232
|
+
args: [{
|
|
233
|
+
logId,
|
|
234
|
+
key,
|
|
235
|
+
descriptions: logArgsById[logId].map(without(['value'])),
|
|
236
|
+
groupIds: groupIds.slice(),
|
|
237
|
+
...extra,
|
|
238
|
+
}]
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Shows the value of the last expression in the code that was run unless
|
|
244
|
+
* it is `undefined`.
|
|
245
|
+
* @param {*} value
|
|
246
|
+
*/
|
|
247
|
+
function reportResult(value) {
|
|
248
|
+
// Like the browser, strings are quoted (but logged strings aren't).
|
|
249
|
+
if (value !== undefined) addLog('result', [value], {}, 'string' === typeof value ? 1 : 0);
|
|
115
250
|
}
|
|
116
251
|
|
|
117
252
|
addEventListener('error', evt => {
|
|
@@ -127,12 +262,17 @@
|
|
|
127
262
|
* @param {boolean=} isInPromise
|
|
128
263
|
*/
|
|
129
264
|
function reportUncaught(error, isInPromise) {
|
|
265
|
+
// Points out how to use top-level await if it was used in a classic block.
|
|
266
|
+
const hint = runningBlockType === 'classic' && error?.name === 'SyntaxError' && /\bawait\b/.test(error.message)
|
|
267
|
+
? '\n(To use top-level await, add data-block-type="module" to the script tag.)'
|
|
268
|
+
: '';
|
|
130
269
|
send({
|
|
131
270
|
target: 'viewer',
|
|
132
271
|
func: 'appendError',
|
|
133
272
|
args: [{
|
|
134
273
|
message: (isInPromise ? 'Uncaught (in promise) ' : 'Uncaught ')
|
|
135
|
-
+ cleanStack(error?.stack ?? `${error?.message ?? error}`)
|
|
274
|
+
+ cleanStack(error?.stack ?? `${error?.message ?? error}`)
|
|
275
|
+
+ hint,
|
|
136
276
|
}]
|
|
137
277
|
});
|
|
138
278
|
}
|
|
@@ -147,7 +287,14 @@
|
|
|
147
287
|
return `${stack}`
|
|
148
288
|
.split('\n')
|
|
149
289
|
.filter(line => !(ownUrl && /^\s*at\b/.test(line) && line.includes(ownUrl)))
|
|
150
|
-
.join('\n')
|
|
290
|
+
.join('\n')
|
|
291
|
+
// Undoes the shift in columns caused by wrapping the last expression.
|
|
292
|
+
.replace(/(snippet-\d+\.js):(\d+):(\d+)/g, (match, name, line, column) => {
|
|
293
|
+
const info = columnShiftsBySnippet[name];
|
|
294
|
+
return info && +line === info.line && +column > info.column
|
|
295
|
+
? `${name}:${line}:${Math.max(info.column, column - info.shift)}`
|
|
296
|
+
: match;
|
|
297
|
+
});
|
|
151
298
|
}
|
|
152
299
|
|
|
153
300
|
function without(props, obj) {
|
|
@@ -278,7 +425,7 @@
|
|
|
278
425
|
function getPreviewParts(value, depth, typeName = getTypeName(value)) {
|
|
279
426
|
if (typeName === 'string') {
|
|
280
427
|
if (!depth) return [['text', value]];
|
|
281
|
-
if (depth > 1 && value.length > 100) value = value.slice(0, 99) + '
|
|
428
|
+
if (depth > 1 && value.length > 100) value = value.slice(0, 99) + '\u2026';
|
|
282
429
|
return [['string', quote(value)]];
|
|
283
430
|
}
|
|
284
431
|
if (typeName === 'number') return [['number', Object.is(value, -0) ? '-0' : '' + value]];
|
|
@@ -325,10 +472,10 @@
|
|
|
325
472
|
// Like the browser, show the source of functions passed directly to a
|
|
326
473
|
// console function.
|
|
327
474
|
if (!depth) {
|
|
328
|
-
return [['text', isClass ? source : source.replace(/^(async\s+)?function\b\s*/, '$1
|
|
475
|
+
return [['text', isClass ? source : source.replace(/^(async\s+)?function\b\s*/, '$1\u0192 ')]];
|
|
329
476
|
}
|
|
330
|
-
if (depth > 1) return [['function', '
|
|
331
|
-
return [['function', isClass ? `class ${value.name}` :
|
|
477
|
+
if (depth > 1) return [['function', '\u0192']];
|
|
478
|
+
return [['function', isClass ? `class ${value.name}` : `\u0192 ${value.name}()`]];
|
|
332
479
|
}
|
|
333
480
|
|
|
334
481
|
/**
|
|
@@ -345,6 +492,8 @@
|
|
|
345
492
|
return [['text', isNaN(value) ? 'Invalid Date' : Date.prototype.toString.call(value)]];
|
|
346
493
|
}
|
|
347
494
|
if (typeName === 'RegExp') return [['regexp', '' + value]];
|
|
495
|
+
// The state of a promise can't be determined synchronously.
|
|
496
|
+
if (typeName === 'Promise') return [['text', `${className} {\u2026}`]];
|
|
348
497
|
// DOM nodes (only available in window mode) are shown like the browser
|
|
349
498
|
// shows them in previews (eg. "h2#title.big").
|
|
350
499
|
if ('undefined' !== typeof Node && value instanceof Node) {
|
|
@@ -364,7 +513,7 @@
|
|
|
364
513
|
if (depth > 1) {
|
|
365
514
|
if (isArrayLike) return [['text', `${className}(${value.length})`]];
|
|
366
515
|
if (typeName === 'Map' || typeName === 'Set') return [['text', `${className}(${value.size})`]];
|
|
367
|
-
return [['text', className === 'Object' ? '{
|
|
516
|
+
return [['text', className === 'Object' ? '{\u2026}' : className]];
|
|
368
517
|
}
|
|
369
518
|
|
|
370
519
|
const parts = [];
|
|
@@ -373,7 +522,7 @@
|
|
|
373
522
|
if (index) parts.push(['text', ', ']);
|
|
374
523
|
addItem(item);
|
|
375
524
|
});
|
|
376
|
-
if (total > items.length) parts.push(['text', (items.length ? ', ' : '') + '
|
|
525
|
+
if (total > items.length) parts.push(['text', (items.length ? ', ' : '') + '\u2026']);
|
|
377
526
|
};
|
|
378
527
|
|
|
379
528
|
if (isArrayLike) {
|
|
@@ -413,7 +562,7 @@
|
|
|
413
562
|
parts.push(
|
|
414
563
|
['key', key],
|
|
415
564
|
['text', ': '],
|
|
416
|
-
...('value' in descriptor ? getPreviewParts(descriptor.value, 2) : [['text', '(
|
|
565
|
+
...('value' in descriptor ? getPreviewParts(descriptor.value, 2) : [['text', '(\u2026)']])
|
|
417
566
|
);
|
|
418
567
|
});
|
|
419
568
|
parts.push(['text', '}']);
|
|
@@ -501,30 +650,64 @@
|
|
|
501
650
|
// }
|
|
502
651
|
|
|
503
652
|
/**
|
|
504
|
-
* Runs
|
|
505
|
-
* shared between all of the code that is run.
|
|
653
|
+
* Runs a block of code.
|
|
506
654
|
* @param {string} jsCode
|
|
655
|
+
* @param {Object=} runOptions
|
|
656
|
+
* @param {"classic"|"module"=} runOptions.blockType
|
|
657
|
+
* "classic" (the default) runs the code as a classic script so that
|
|
658
|
+
* top-level declarations are shared between all of the code that is run.
|
|
659
|
+
* "module" runs the code as a module which allows for top-level await and
|
|
660
|
+
* import statements, but top-level declarations stay in the module.
|
|
661
|
+
* @param {[number, number]=} runOptions.resultRange
|
|
662
|
+
* The start and end index of the last expression in the code whose value
|
|
663
|
+
* should be shown.
|
|
507
664
|
*/
|
|
508
|
-
function runCode(jsCode) {
|
|
665
|
+
async function runCode(jsCode, runOptions) {
|
|
666
|
+
const {blockType, resultRange} = Object(runOptions);
|
|
667
|
+
|
|
509
668
|
// Names the code so that stack traces refer to it by this name.
|
|
510
|
-
const
|
|
669
|
+
const snippetName = `snippet-${++snippetCount}.js`;
|
|
511
670
|
|
|
512
|
-
|
|
513
|
-
|
|
671
|
+
// Wraps the last expression so that its value is reported. This keeps
|
|
672
|
+
// everything on the same lines so that line numbers in errors still match
|
|
673
|
+
// and the shift in columns is recorded so they can be fixed too.
|
|
674
|
+
if (Array.isArray(resultRange)) {
|
|
675
|
+
const [start, end] = resultRange;
|
|
676
|
+
const prefix = `globalThis[Symbol.for(${JSON.stringify(RESULT_KEY)})]((`;
|
|
677
|
+
const linesBefore = jsCode.slice(0, start).split('\n');
|
|
678
|
+
columnShiftsBySnippet[snippetName] = {
|
|
679
|
+
line: linesBefore.length,
|
|
680
|
+
column: linesBefore[linesBefore.length - 1].length + 1,
|
|
681
|
+
shift: prefix.length,
|
|
682
|
+
};
|
|
683
|
+
jsCode = jsCode.slice(0, start) + prefix + jsCode.slice(start, end) + '))' + jsCode.slice(end);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const source = `${jsCode}\n//# sourceURL=${snippetName}`;
|
|
687
|
+
|
|
688
|
+
runningBlockType = blockType === 'module' ? 'module' : 'classic';
|
|
689
|
+
try {
|
|
690
|
+
if (blockType === 'module') {
|
|
691
|
+
// Waits for the module to finish (including any top-level await).
|
|
692
|
+
await import('data:text/javascript;charset=utf-8,' + encodeURIComponent(source));
|
|
693
|
+
}
|
|
694
|
+
else if (mode === 'worker') {
|
|
514
695
|
importCode(source);
|
|
515
696
|
}
|
|
516
|
-
|
|
517
|
-
|
|
697
|
+
else {
|
|
698
|
+
const script = document.createElement('script');
|
|
699
|
+
script.textContent = source;
|
|
700
|
+
document.head.appendChild(script);
|
|
701
|
+
script.remove();
|
|
518
702
|
}
|
|
519
703
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
script.textContent = source;
|
|
523
|
-
document.head.appendChild(script);
|
|
524
|
-
script.remove();
|
|
704
|
+
catch (e) {
|
|
705
|
+
reportUncaught(e);
|
|
525
706
|
}
|
|
707
|
+
runningBlockType = undefined;
|
|
526
708
|
|
|
527
|
-
// Lets the viewer know that
|
|
709
|
+
// Lets the viewer know that the code finished running and that any logs
|
|
710
|
+
// made while it ran have already been sent.
|
|
528
711
|
send({target: 'viewer', func: 'onCodeRan', args: []});
|
|
529
712
|
}
|
|
530
713
|
|
|
@@ -592,6 +775,7 @@
|
|
|
592
775
|
*/
|
|
593
776
|
function clearLogs() {
|
|
594
777
|
for (const logId of Object.keys(logArgsById)) delete logArgsById[logId];
|
|
778
|
+
groupIds = [];
|
|
595
779
|
}
|
|
596
780
|
|
|
597
781
|
return {clearLogs, runCode, sendDescriptionFor};
|
|
@@ -705,6 +889,9 @@
|
|
|
705
889
|
function main(script) {
|
|
706
890
|
const dataset = JSON.parse(JSON.stringify(script.dataset));
|
|
707
891
|
const runnerMode = dataset.runner === 'window' ? 'window' : 'worker';
|
|
892
|
+
const blockType = dataset.blockType === 'module' ? 'module' : 'classic';
|
|
893
|
+
const showResults = dataset.showResults !== 'false';
|
|
894
|
+
const libraryUrl = getLibraryFileUrl.bind(null, dataset.librariesUrl || DEFAULT_LIBRARIES_URL);
|
|
708
895
|
|
|
709
896
|
/** @type {ReturnType<createCallableFrame>} */
|
|
710
897
|
let callableViewerFrame;
|
|
@@ -722,9 +909,18 @@
|
|
|
722
909
|
jsCode() {
|
|
723
910
|
let mountedApp;
|
|
724
911
|
|
|
912
|
+
/**
|
|
913
|
+
* Indicates if any of the libraries that the viewer needs failed to load (eg.
|
|
914
|
+
* because the CDN is blocked or the user is offline).
|
|
915
|
+
*/
|
|
916
|
+
const IS_MISSING_LIBRARIES = !window.Vue || !window.ace || !window.Prism;
|
|
917
|
+
if (IS_MISSING_LIBRARIES) {
|
|
918
|
+
document.querySelector('#splash').classList.add('failed');
|
|
919
|
+
}
|
|
920
|
+
|
|
725
921
|
const Prism = window.Prism;
|
|
726
922
|
delete window.Prism;
|
|
727
|
-
Prism
|
|
923
|
+
Prism?.plugins.autoloader.loadLanguages('javascript');
|
|
728
924
|
|
|
729
925
|
/**
|
|
730
926
|
* The minimum amount of time (in milliseconds) that the loading screen is shown.
|
|
@@ -752,6 +948,8 @@
|
|
|
752
948
|
* @param {{runnerMode: "worker"|"window", packageInfo: {name: string, version: string, homepage: string, repoUrl: string, bugsUrl: string}}} meta
|
|
753
949
|
*/
|
|
754
950
|
function init(jsCode, dataset, meta) {
|
|
951
|
+
if (IS_MISSING_LIBRARIES) return;
|
|
952
|
+
|
|
755
953
|
const hidePrefix = dataset.hidePrefix ?? '';
|
|
756
954
|
const darkSchemeQuery = matchMedia('(prefers-color-scheme: dark)');
|
|
757
955
|
const originalCode = unindentMin(jsCode);
|
|
@@ -767,7 +965,10 @@
|
|
|
767
965
|
hidePrefix,
|
|
768
966
|
hiddenGroups: copyHiddenGroups(),
|
|
769
967
|
runnerMode: meta.runnerMode,
|
|
968
|
+
blockType: meta.blockType,
|
|
969
|
+
showResults: meta.showResults,
|
|
770
970
|
packageInfo: meta.packageInfo,
|
|
971
|
+
libraryVersions: meta.libraryVersions,
|
|
771
972
|
// Every group of code that was run (in order) which is used when
|
|
772
973
|
// copying the console as HTML. Unlike the displays this is only
|
|
773
974
|
// cleared by a reset.
|
|
@@ -817,6 +1018,22 @@
|
|
|
817
1018
|
const name = this.theme === 'dark' ? 'Dark' : 'Light';
|
|
818
1019
|
return `${name} (${this.forcedTheme ? 'set by data-theme' : 'follows your system'})`;
|
|
819
1020
|
},
|
|
1021
|
+
blockTypeDescription() {
|
|
1022
|
+
return this.blockType === 'module'
|
|
1023
|
+
? 'Modules (top-level await and imports work, declarations stay in each block)'
|
|
1024
|
+
: 'Classic scripts (top-level declarations are shared between blocks)';
|
|
1025
|
+
},
|
|
1026
|
+
/**
|
|
1027
|
+
* The IDs of the groups (from console.groupCollapsed() or a group that
|
|
1028
|
+
* the user collapsed) whose messages should be hidden.
|
|
1029
|
+
*/
|
|
1030
|
+
collapsedGroupIds() {
|
|
1031
|
+
return new Set(
|
|
1032
|
+
this.displays
|
|
1033
|
+
.filter(d => d.groupId && d.isCollapsed)
|
|
1034
|
+
.map(d => d.groupId)
|
|
1035
|
+
);
|
|
1036
|
+
},
|
|
820
1037
|
layoutDescription() {
|
|
821
1038
|
return (this.dividerOrient === 'vertical' ? 'Editor beside the console' : 'Editor below the console')
|
|
822
1039
|
+ (this.isDividerOrientAuto ? ' (automatic)' : '');
|
|
@@ -864,10 +1081,19 @@
|
|
|
864
1081
|
blocks.push(...pendingHiddenGroups.map(group => group.allLines));
|
|
865
1082
|
return joinCodeBlocks(blocks);
|
|
866
1083
|
},
|
|
1084
|
+
/**
|
|
1085
|
+
* The data attributes for the copied console. data-libraries-url is
|
|
1086
|
+
* left out because it usually points to files on this site while the
|
|
1087
|
+
* copied console loads JS Box from a CDN.
|
|
1088
|
+
*/
|
|
1089
|
+
exportDataset() {
|
|
1090
|
+
const {librariesUrl, ...exportDataset} = dataset;
|
|
1091
|
+
return exportDataset;
|
|
1092
|
+
},
|
|
867
1093
|
exportHtml() {
|
|
868
1094
|
return buildConsoleHtml({
|
|
869
1095
|
code: this.exportJsCode,
|
|
870
|
-
dataset,
|
|
1096
|
+
dataset: this.exportDataset,
|
|
871
1097
|
packageInfo: this.packageInfo,
|
|
872
1098
|
isFullPage: this.exportFormat === 'page',
|
|
873
1099
|
});
|
|
@@ -973,7 +1199,18 @@
|
|
|
973
1199
|
});
|
|
974
1200
|
this.runningCount++;
|
|
975
1201
|
this.runHistory.push(group.allLines);
|
|
976
|
-
messageParent({
|
|
1202
|
+
messageParent({
|
|
1203
|
+
target: 'runner',
|
|
1204
|
+
func: 'runCode',
|
|
1205
|
+
args: [group.lines, {
|
|
1206
|
+
blockType: this.blockType,
|
|
1207
|
+
// Like the browser's console, show the value of the last
|
|
1208
|
+
// expression (except for hidden code).
|
|
1209
|
+
resultRange: this.showResults && !isHidden
|
|
1210
|
+
? findLastExpression(group.lines, this.blockType)
|
|
1211
|
+
: null,
|
|
1212
|
+
}],
|
|
1213
|
+
});
|
|
977
1214
|
},
|
|
978
1215
|
clearConsole() {
|
|
979
1216
|
this.displays = [];
|
|
@@ -1062,7 +1299,7 @@
|
|
|
1062
1299
|
downloadExport() {
|
|
1063
1300
|
const html = buildConsoleHtml({
|
|
1064
1301
|
code: this.exportJsCode,
|
|
1065
|
-
dataset,
|
|
1302
|
+
dataset: this.exportDataset,
|
|
1066
1303
|
packageInfo: this.packageInfo,
|
|
1067
1304
|
isFullPage: true,
|
|
1068
1305
|
});
|
|
@@ -1070,6 +1307,24 @@
|
|
|
1070
1307
|
Object.assign(document.createElement('a'), {href: url, download: 'js-box.html'}).click();
|
|
1071
1308
|
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
1072
1309
|
},
|
|
1310
|
+
/**
|
|
1311
|
+
* Clicking a group's header (from console.group()) collapses or expands
|
|
1312
|
+
* the group unless a value in the header was clicked.
|
|
1313
|
+
* @param {*} display
|
|
1314
|
+
* @param {MouseEvent} evt
|
|
1315
|
+
*/
|
|
1316
|
+
onLogRowClick(display, evt) {
|
|
1317
|
+
if (display.groupId && !evt.target.closest('.js-value-header.expandable')) {
|
|
1318
|
+
display.isCollapsed = !display.isCollapsed;
|
|
1319
|
+
}
|
|
1320
|
+
},
|
|
1321
|
+
/**
|
|
1322
|
+
* Indicates if a display is inside of a collapsed group.
|
|
1323
|
+
*/
|
|
1324
|
+
isInCollapsedGroup(display) {
|
|
1325
|
+
const {collapsedGroupIds} = this;
|
|
1326
|
+
return !!collapsedGroupIds.size && !!display.groupIds?.some(id => collapsedGroupIds.has(id));
|
|
1327
|
+
},
|
|
1073
1328
|
closeDialog(isConfirmed) {
|
|
1074
1329
|
const {dialog} = this;
|
|
1075
1330
|
this.dialog = null;
|
|
@@ -1390,6 +1645,7 @@
|
|
|
1390
1645
|
// https://icon-sets.iconify.design/mdi/error-outline/
|
|
1391
1646
|
error: '<svg viewBox="0 0 24 24"><path fill="currentColor" d="M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8"/></svg>',
|
|
1392
1647
|
// Icons similar to those in the browser's console.
|
|
1648
|
+
result: '<svg viewBox="0 0 16 16"><path d="M9 4.5L5.5 8 9 11.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="8" r="1.1" fill="currentColor"/></svg>',
|
|
1393
1649
|
close: '<svg viewBox="0 0 16 16"><path d="M4 4l8 8m0-8l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',
|
|
1394
1650
|
clear: '<svg viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M3.75 12.25l8.5-8.5" stroke="currentColor" stroke-width="1.5"/></svg>',
|
|
1395
1651
|
copyToEditor: '<svg viewBox="0 0 16 16"><path d="M6 3.5L2.5 7 6 10.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 7h6.5a4 4 0 0 1 4 4v1.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
|
|
@@ -1573,6 +1829,33 @@
|
|
|
1573
1829
|
};
|
|
1574
1830
|
}
|
|
1575
1831
|
|
|
1832
|
+
/**
|
|
1833
|
+
* Finds the last statement in the code if it is an expression so that its
|
|
1834
|
+
* value can be shown.
|
|
1835
|
+
* @param {string} code
|
|
1836
|
+
* @param {"classic"|"module"} blockType
|
|
1837
|
+
* @returns {[number, number]|null}
|
|
1838
|
+
* The start and end index of the expression or `null` if the last statement
|
|
1839
|
+
* isn't an expression (or the code can't be parsed, in which case running
|
|
1840
|
+
* it will show the syntax error).
|
|
1841
|
+
*/
|
|
1842
|
+
function findLastExpression(code, blockType) {
|
|
1843
|
+
if (!window.acorn) return null;
|
|
1844
|
+
try {
|
|
1845
|
+
const {body} = acorn.parse(code, {
|
|
1846
|
+
ecmaVersion: 'latest',
|
|
1847
|
+
sourceType: blockType === 'module' ? 'module' : 'script',
|
|
1848
|
+
allowHashBang: true,
|
|
1849
|
+
});
|
|
1850
|
+
const last = body[body.length - 1];
|
|
1851
|
+
if (last?.type === 'ExpressionStatement' && !last.directive) {
|
|
1852
|
+
return [last.expression.start, last.expression.end];
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
catch (e) {}
|
|
1856
|
+
return null;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1576
1859
|
/**
|
|
1577
1860
|
* Joins groups of code back together making sure that each one (other than
|
|
1578
1861
|
* the first) starts with a header so that they stay separate groups.
|
|
@@ -1733,12 +2016,12 @@
|
|
|
1733
2016
|
* @param {any[]} options.descriptions
|
|
1734
2017
|
* @param {{headers: string[], rows: {index: string, cells: any[]}[]}|null} options.table
|
|
1735
2018
|
*/
|
|
1736
|
-
function appendLog({logId, key, descriptions, table}) {
|
|
2019
|
+
function appendLog({logId, key, descriptions, table, groupIds, groupId, isCollapsed, stack}) {
|
|
1737
2020
|
key = `${key}`;
|
|
1738
2021
|
mountedApp.displays.push({
|
|
1739
2022
|
type: 'log',
|
|
1740
2023
|
key,
|
|
1741
|
-
name: `console.${key}`,
|
|
2024
|
+
name: key === 'result' ? 'Result' : `console.${key}`,
|
|
1742
2025
|
descriptions: Array.from(descriptions, sanitizeDescription),
|
|
1743
2026
|
table: table ? {
|
|
1744
2027
|
headers: Array.from(table.headers, h => `${h}`),
|
|
@@ -1747,6 +2030,12 @@
|
|
|
1747
2030
|
cells: Array.from(row.cells, cell => cell && sanitizeDescription(cell)),
|
|
1748
2031
|
})),
|
|
1749
2032
|
} : null,
|
|
2033
|
+
// The groups (from console.group()) that this message is in.
|
|
2034
|
+
groupIds: Array.from(groupIds ?? [], id => `${id}`),
|
|
2035
|
+
// Set if this message starts a group.
|
|
2036
|
+
groupId: groupId != null ? `${groupId}` : null,
|
|
2037
|
+
isCollapsed: !!isCollapsed,
|
|
2038
|
+
stack: stack != null ? `${stack}` : null,
|
|
1750
2039
|
logId: `${logId}`,
|
|
1751
2040
|
});
|
|
1752
2041
|
}
|
|
@@ -1814,17 +2103,23 @@
|
|
|
1814
2103
|
}
|
|
1815
2104
|
|
|
1816
2105
|
},
|
|
2106
|
+
// Exact versions are used so that a new release of a library can never
|
|
2107
|
+
// change how an existing version of this console works. Ace and Prism
|
|
2108
|
+
// load other files (eg. language modes) from next to these files.
|
|
1817
2109
|
jsUrls: [
|
|
1818
|
-
'
|
|
1819
|
-
'
|
|
1820
|
-
'
|
|
1821
|
-
'
|
|
1822
|
-
'
|
|
2110
|
+
libraryUrl('vue', 'dist/vue.global.prod.js'),
|
|
2111
|
+
libraryUrl('ace-builds', 'src-noconflict/ace.js'),
|
|
2112
|
+
libraryUrl('prismjs', 'components/prism-core.min.js'),
|
|
2113
|
+
libraryUrl('prismjs', 'plugins/autoloader/prism-autoloader.min.js'),
|
|
2114
|
+
libraryUrl('prismjs', 'plugins/match-braces/prism-match-braces.min.js'),
|
|
2115
|
+
// Used to find the last expression in each block of code so that its
|
|
2116
|
+
// value can be shown.
|
|
2117
|
+
...(showResults ? [libraryUrl('acorn', 'dist/acorn.js')] : []),
|
|
1823
2118
|
],
|
|
1824
2119
|
cssUrls: [
|
|
1825
2120
|
'data:text/css,' + encodeURIComponent(VIEWER_IFRAME_CSS),
|
|
1826
|
-
'
|
|
1827
|
-
'
|
|
2121
|
+
libraryUrl('prism-themes', 'themes/prism-vsc-dark-plus.min.css'),
|
|
2122
|
+
libraryUrl('prismjs', 'plugins/match-braces/prism-match-braces.min.css'),
|
|
1828
2123
|
],
|
|
1829
2124
|
htmlAttributes: 'data-theme="' + theme + '"',
|
|
1830
2125
|
onMessage(message) {
|
|
@@ -1833,7 +2128,7 @@
|
|
|
1833
2128
|
async onReady() {
|
|
1834
2129
|
// The dataset is passed as is so that the viewer knows which options
|
|
1835
2130
|
// were actually specified (eg. when copying the console as HTML).
|
|
1836
|
-
this.call('init', script.textContent, dataset, {runnerMode, packageInfo: PACKAGE_INFO});
|
|
2131
|
+
this.call('init', script.textContent, dataset, {runnerMode, blockType, showResults, packageInfo: PACKAGE_INFO, libraryVersions: LIBRARY_VERSIONS});
|
|
1837
2132
|
},
|
|
1838
2133
|
body: VIEWER_IFRAME_HTML,
|
|
1839
2134
|
style: {
|