yourjs-box 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -2
- package/dist/yourjs-box.full.js +195 -23
- package/dist/yourjs-box.js +156 -16
- package/dist/yourjs-box.min.js +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,8 @@ should be loaded into the console inside of the script tag:
|
|
|
24
24
|
</div>
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
The console fills its container
|
|
27
|
+
The console fills its container (and is never less than 150px tall). The
|
|
28
|
+
script is also available from unpkg at
|
|
28
29
|
`https://unpkg.com/yourjs-box@1/dist/yourjs-box.min.js`.
|
|
29
30
|
|
|
30
31
|
### Code Blocks
|
|
@@ -41,6 +42,8 @@ If the editor already has code in it you are asked before it is replaced.
|
|
|
41
42
|
|
|
42
43
|
- **Clear** removes everything from the console. Code can also call
|
|
43
44
|
`console.clear()`.
|
|
45
|
+
- The **layout** button switches between showing the editor beside or below
|
|
46
|
+
the console.
|
|
44
47
|
- **Full screen** shows the console using the whole screen. If the browser
|
|
45
48
|
doesn't allow that (eg. on an iPhone) the console fills the browser window
|
|
46
49
|
instead. Press <kbd>Esc</kbd> to exit.
|
|
@@ -49,7 +52,6 @@ If the editor already has code in it you are asked before it is replaced.
|
|
|
49
52
|
console's settings and keyboard shortcuts.
|
|
50
53
|
- The **⋯** button opens a menu with:
|
|
51
54
|
- **Text size**, which is remembered for every console on the same site.
|
|
52
|
-
- **Editor beside / below the console** to change the layout.
|
|
53
55
|
- **Pop out into a window**, which moves the console into a separate window
|
|
54
56
|
while the code keeps running in the page (so in window mode the code can
|
|
55
57
|
still change the page). Close the window or click **Bring it back** to
|
|
@@ -139,6 +141,43 @@ up in the output as a collapsed block labelled with whatever came after the
|
|
|
139
141
|
prefix (and optional colon), or "Hidden code" if nothing did. Clicking the
|
|
140
142
|
label shows or hides the code.
|
|
141
143
|
|
|
144
|
+
### JavaScript API
|
|
145
|
+
|
|
146
|
+
Loading the script also provides `YourJSBox` for creating consoles from
|
|
147
|
+
JavaScript (eg. in React, Vue or any page that adds content dynamically). A
|
|
148
|
+
script tag in the `<head>` only provides the API, while one in the `<body>` is
|
|
149
|
+
also replaced by a console (with the code inside of it) as usual.
|
|
150
|
+
|
|
151
|
+
```html
|
|
152
|
+
<script src="https://cdn.jsdelivr.net/npm/yourjs-box@1/dist/yourjs-box.min.js"></script>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
const box = YourJSBox.create({
|
|
157
|
+
target: '#lesson',
|
|
158
|
+
code: "// Say hello \\\\\nconsole.log('Hello!');",
|
|
159
|
+
theme: 'dark',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Later (eg. when a component is removed):
|
|
163
|
+
box.destroy();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`YourJSBox.create(options)` returns `{element, destroy}` where `element` is the
|
|
167
|
+
console's IFRAME and `destroy()` removes it, stops its code and closes its
|
|
168
|
+
pop-out window (in window mode it also restores the page's `console`
|
|
169
|
+
functions). The options are:
|
|
170
|
+
|
|
171
|
+
| Option | Description |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| `target` | Required. The element (or a CSS selector for it) that the console is placed relative to. |
|
|
174
|
+
| `placement` | Where the console goes: `"fill"` (default) replaces the target's contents, `"append"` and `"prepend"` add it inside of the target, `"replace"` replaces the target itself and `"before"` and `"after"` add it next to the target. |
|
|
175
|
+
| `height` | The CSS height of the console (a number is treated as pixels). Defaults to `"100%"` so the console fills its container. It is never less than 150px. |
|
|
176
|
+
| `code` | The code that the console starts with. |
|
|
177
|
+
| `runner`, `blockType`, `showResults`, `hidePrefix`, `dividerOrient`, `theme`, `librariesUrl` | The same as the `data-*` attributes above. |
|
|
178
|
+
|
|
179
|
+
`YourJSBox.version` is the version of JS Box that was loaded.
|
|
180
|
+
|
|
142
181
|
### Where Code Runs
|
|
143
182
|
|
|
144
183
|
The console's interface is always in an IFRAME so that its styles and the
|
package/dist/yourjs-box.full.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! yourjs-box v1.
|
|
1
|
+
/*! yourjs-box v1.4.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
|
|
@@ -9,12 +9,12 @@
|
|
|
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.4.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><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\" :font-size=\"Math.round(12 * textScale)\" @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=\"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=\"menuitemradio\" :aria-checked=\"dividerOrient === 'vertical'\" @click=\"setDividerOrient('vertical'); closeMenu(true)\"><span class=\"menu-icon\"><icon v-if=\"dividerOrient === 'vertical'\" name=\"check\"></icon></span>Editor beside the console</button> <button class=\"menu-item\" role=\"menuitemradio\" :aria-checked=\"dividerOrient === 'horizontal'\" @click=\"setDividerOrient('horizontal'); closeMenu(true)\"><span class=\"menu-icon\"><icon v-if=\"dividerOrient === 'horizontal'\" name=\"check\"></icon></span>Editor below the console</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=\"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>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, the menu or full screen</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>";
|
|
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\" :font-size=\"Math.round(12 * textScale)\" @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=\"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 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=\"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>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, the menu or full screen</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.
|
|
@@ -130,6 +130,9 @@
|
|
|
130
130
|
const RESULT_KEY = `yourjs-box.result.${Math.random().toString(36).slice(2)}`;
|
|
131
131
|
globalThis[Symbol.for(RESULT_KEY)] = reportResult;
|
|
132
132
|
|
|
133
|
+
// The console functions that were replaced so that they can be restored.
|
|
134
|
+
const consoleWrappers = [];
|
|
135
|
+
|
|
133
136
|
// Overrides for console functions. In window mode this also captures
|
|
134
137
|
// anything else that the page logs, just like the browser's console.
|
|
135
138
|
for (const key of [
|
|
@@ -139,12 +142,14 @@
|
|
|
139
142
|
]) {
|
|
140
143
|
const original = console[key];
|
|
141
144
|
if ('function' !== typeof original) continue;
|
|
142
|
-
|
|
145
|
+
const wrapper = function(...args) {
|
|
143
146
|
handleConsoleCall(key, args);
|
|
144
147
|
|
|
145
148
|
// Calls and returns the original console function.
|
|
146
149
|
return original.apply(this, arguments);
|
|
147
150
|
};
|
|
151
|
+
console[key] = wrapper;
|
|
152
|
+
consoleWrappers.push({key, original, wrapper});
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
/**
|
|
@@ -251,13 +256,29 @@
|
|
|
251
256
|
if (value !== undefined) addLog('result', [value], {}, 'string' === typeof value ? 1 : 0);
|
|
252
257
|
}
|
|
253
258
|
|
|
254
|
-
|
|
259
|
+
function onError(evt) {
|
|
255
260
|
reportUncaught(evt.error !== undefined ? evt.error : evt.message);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
addEventListener('unhandledrejection', evt => {
|
|
261
|
+
}
|
|
262
|
+
function onUnhandledRejection(evt) {
|
|
259
263
|
reportUncaught(evt.reason, true);
|
|
260
|
-
}
|
|
264
|
+
}
|
|
265
|
+
addEventListener('error', onError);
|
|
266
|
+
addEventListener('unhandledrejection', onUnhandledRejection);
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Stops capturing what is logged (used when a console in window mode is
|
|
270
|
+
* destroyed). Each console function is restored unless something else has
|
|
271
|
+
* replaced it since.
|
|
272
|
+
*/
|
|
273
|
+
function destroy() {
|
|
274
|
+
removeEventListener('error', onError);
|
|
275
|
+
removeEventListener('unhandledrejection', onUnhandledRejection);
|
|
276
|
+
delete globalThis[Symbol.for(RESULT_KEY)];
|
|
277
|
+
for (const {key, original, wrapper} of consoleWrappers) {
|
|
278
|
+
if (console[key] === wrapper) console[key] = original;
|
|
279
|
+
}
|
|
280
|
+
clearLogs();
|
|
281
|
+
}
|
|
261
282
|
|
|
262
283
|
/**
|
|
263
284
|
* @param {*} error
|
|
@@ -780,7 +801,7 @@
|
|
|
780
801
|
groupIds = [];
|
|
781
802
|
}
|
|
782
803
|
|
|
783
|
-
return {clearLogs, runCode, sendDescriptionFor};
|
|
804
|
+
return {clearLogs, destroy, runCode, sendDescriptionFor};
|
|
784
805
|
}
|
|
785
806
|
|
|
786
807
|
|
|
@@ -850,6 +871,10 @@
|
|
|
850
871
|
start();
|
|
851
872
|
|
|
852
873
|
return {
|
|
874
|
+
destroy() {
|
|
875
|
+
worker.terminate();
|
|
876
|
+
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
|
877
|
+
},
|
|
853
878
|
apply(func, args) {
|
|
854
879
|
if (func === 'reset') {
|
|
855
880
|
worker.terminate();
|
|
@@ -873,6 +898,9 @@
|
|
|
873
898
|
function createWindowRunner(onMessage) {
|
|
874
899
|
const runner = createRunner(onMessage, {mode: 'window', ownUrl: OWN_URL});
|
|
875
900
|
return {
|
|
901
|
+
destroy() {
|
|
902
|
+
runner.destroy();
|
|
903
|
+
},
|
|
876
904
|
apply(func, args) {
|
|
877
905
|
// Anything the code defined stays defined so a reset can only forget
|
|
878
906
|
// the logged values.
|
|
@@ -883,13 +911,20 @@
|
|
|
883
911
|
}
|
|
884
912
|
|
|
885
913
|
/**
|
|
886
|
-
*
|
|
887
|
-
* @param {
|
|
888
|
-
*
|
|
889
|
-
*
|
|
914
|
+
* Creates a console.
|
|
915
|
+
* @param {Object} options
|
|
916
|
+
* @param {string} options.code
|
|
917
|
+
* The code that the console starts with.
|
|
918
|
+
* @param {{[name: string]: string}} options.dataset
|
|
919
|
+
* The options for the console in the same form as the data attributes of
|
|
920
|
+
* a script tag (eg. `{runner: 'window', hidePrefix: 'HIDE'}`).
|
|
921
|
+
* @param {string=} options.height
|
|
922
|
+
* Optional, defaults to `"100%"`. The CSS height of the console.
|
|
923
|
+
* @param {(element: HTMLIFrameElement) => void} options.insert
|
|
924
|
+
* Puts the console's element into the page.
|
|
925
|
+
* @returns {{element: HTMLIFrameElement, destroy: () => void}}
|
|
890
926
|
*/
|
|
891
|
-
function
|
|
892
|
-
const dataset = JSON.parse(JSON.stringify(script.dataset));
|
|
927
|
+
function createConsole({code, dataset, height, insert}) {
|
|
893
928
|
const runnerMode = dataset.runner === 'window' ? 'window' : 'worker';
|
|
894
929
|
const blockType = dataset.blockType === 'module' ? 'module' : 'classic';
|
|
895
930
|
const showResults = dataset.showResults !== 'false';
|
|
@@ -993,7 +1028,8 @@
|
|
|
993
1028
|
};
|
|
994
1029
|
|
|
995
1030
|
// The pop-out window can't work without this page so it is closed too.
|
|
996
|
-
|
|
1031
|
+
const closePopOut = () => popOutWindow?.close();
|
|
1032
|
+
addEventListener('pagehide', closePopOut);
|
|
997
1033
|
|
|
998
1034
|
/**
|
|
999
1035
|
* @param {Window=} targetWindow
|
|
@@ -1246,6 +1282,18 @@
|
|
|
1246
1282
|
title: 'Clear console',
|
|
1247
1283
|
callback() { this.clearConsole(); },
|
|
1248
1284
|
},
|
|
1285
|
+
{
|
|
1286
|
+
iconName: 'horizontalView',
|
|
1287
|
+
title: 'Show the editor below the console',
|
|
1288
|
+
callback() { this.setDividerOrient('horizontal'); },
|
|
1289
|
+
showIf() { return this.dividerOrient !== 'horizontal'; }
|
|
1290
|
+
},
|
|
1291
|
+
{
|
|
1292
|
+
iconName: 'verticalView',
|
|
1293
|
+
title: 'Show the editor beside the console',
|
|
1294
|
+
callback() { this.setDividerOrient('vertical'); },
|
|
1295
|
+
showIf() { return this.dividerOrient !== 'vertical'; }
|
|
1296
|
+
},
|
|
1249
1297
|
{
|
|
1250
1298
|
iconName: isFullscreen ? 'exitFullscreen' : 'fullscreen',
|
|
1251
1299
|
title: isFullscreen ? 'Exit full screen' : 'Full screen',
|
|
@@ -1265,7 +1313,7 @@
|
|
|
1265
1313
|
callback() { this.runCode(); },
|
|
1266
1314
|
disableIf() { return !this.canRunCode; }
|
|
1267
1315
|
},
|
|
1268
|
-
];
|
|
1316
|
+
].filter(btn => !btn.showIf || btn.showIf.call(this));
|
|
1269
1317
|
},
|
|
1270
1318
|
canShrinkText() {
|
|
1271
1319
|
return this.textScale > TEXT_SCALES[0];
|
|
@@ -1947,7 +1995,6 @@
|
|
|
1947
1995
|
fullscreen: '<svg viewBox="0 0 16 16"><path d="M2.5 6V2.5H6M10 2.5h3.5V6M13.5 10v3.5H10M6 13.5H2.5V10" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
1948
1996
|
exitFullscreen: '<svg viewBox="0 0 16 16"><path d="M6 2.5V6H2.5M13.5 6H10V2.5M10 13.5V10h3.5M2.5 10H6v3.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
1949
1997
|
more: '<svg viewBox="0 0 16 16"><circle cx="3.5" cy="8" r="1.4" fill="currentColor"/><circle cx="8" cy="8" r="1.4" fill="currentColor"/><circle cx="12.5" cy="8" r="1.4" fill="currentColor"/></svg>',
|
|
1950
|
-
check: '<svg viewBox="0 0 16 16"><path d="M3.5 8.5l3 3 6-7" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
1951
1998
|
popOut: '<svg viewBox="0 0 16 16"><path d="M9.5 2.5h4v4M13.5 2.5L8 8M12 9.5v3a1 1 0 0 1-1 1H3.5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
1952
1999
|
popIn: '<svg viewBox="0 0 16 16"><path d="M12 8.5H8V4.5M8 8.5l5.5-5.5M12 10.5v2a1 1 0 0 1-1 1H3.5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h2" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
1953
2000
|
code: '<svg viewBox="0 0 16 16"><path d="M5.5 4.5L2 8l3.5 3.5M10.5 4.5L14 8l-3.5 3.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
@@ -2467,7 +2514,7 @@
|
|
|
2467
2514
|
async onReady() {
|
|
2468
2515
|
// The dataset is passed as is so that the viewer knows which options
|
|
2469
2516
|
// were actually specified (eg. when copying the console as HTML).
|
|
2470
|
-
this.call('init',
|
|
2517
|
+
this.call('init', code, dataset, {
|
|
2471
2518
|
runnerMode,
|
|
2472
2519
|
blockType,
|
|
2473
2520
|
showResults,
|
|
@@ -2480,7 +2527,10 @@
|
|
|
2480
2527
|
body: VIEWER_IFRAME_HTML,
|
|
2481
2528
|
style: {
|
|
2482
2529
|
width: '100%',
|
|
2483
|
-
height
|
|
2530
|
+
// Fills its container unless a height is given but is never
|
|
2531
|
+
// squashed smaller than the default height of an IFRAME.
|
|
2532
|
+
height: height || '100%',
|
|
2533
|
+
minHeight: '150px',
|
|
2484
2534
|
border: 0,
|
|
2485
2535
|
display: 'block',
|
|
2486
2536
|
},
|
|
@@ -2488,9 +2538,123 @@
|
|
|
2488
2538
|
}
|
|
2489
2539
|
|
|
2490
2540
|
inlineViewer = activeViewer = createViewer();
|
|
2491
|
-
|
|
2541
|
+
insert(inlineViewer.iframe);
|
|
2542
|
+
|
|
2543
|
+
let isDestroyed = false;
|
|
2544
|
+
return {
|
|
2545
|
+
element: inlineViewer.iframe,
|
|
2546
|
+
/**
|
|
2547
|
+
* Removes the console, stops its code and closes its pop-out window.
|
|
2548
|
+
*/
|
|
2549
|
+
destroy() {
|
|
2550
|
+
if (isDestroyed) return;
|
|
2551
|
+
isDestroyed = true;
|
|
2552
|
+
if (popOutWindow) {
|
|
2553
|
+
clearInterval(popOutWatcher);
|
|
2554
|
+
popOutViewer.dispose();
|
|
2555
|
+
popOutWindow.close();
|
|
2556
|
+
popOutWindow = null;
|
|
2557
|
+
}
|
|
2558
|
+
removeEventListener('pagehide', closePopOut);
|
|
2559
|
+
runner.destroy();
|
|
2560
|
+
inlineViewer.dispose();
|
|
2561
|
+
inlineViewer.iframe.remove();
|
|
2562
|
+
},
|
|
2563
|
+
};
|
|
2492
2564
|
}
|
|
2493
2565
|
|
|
2566
|
+
/**
|
|
2567
|
+
* Creates a console in place of a script tag using its code and data
|
|
2568
|
+
* attributes.
|
|
2569
|
+
* @param {HTMLScriptElement} script
|
|
2570
|
+
*/
|
|
2571
|
+
function createConsoleFromScript(script) {
|
|
2572
|
+
return createConsole({
|
|
2573
|
+
code: script.textContent,
|
|
2574
|
+
dataset: JSON.parse(JSON.stringify(script.dataset)),
|
|
2575
|
+
insert: element => script.parentNode.insertBefore(element, script),
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
/**
|
|
2580
|
+
* Where YourJSBox.create() can put a console relative to its target.
|
|
2581
|
+
*/
|
|
2582
|
+
const PLACEMENTS = {
|
|
2583
|
+
fill: (target, element) => target.replaceChildren(element),
|
|
2584
|
+
append: (target, element) => target.append(element),
|
|
2585
|
+
prepend: (target, element) => target.prepend(element),
|
|
2586
|
+
replace: (target, element) => target.replaceWith(element),
|
|
2587
|
+
before: (target, element) => target.before(element),
|
|
2588
|
+
after: (target, element) => target.after(element),
|
|
2589
|
+
};
|
|
2590
|
+
|
|
2591
|
+
/**
|
|
2592
|
+
* The options of YourJSBox.create() which are the same as the data
|
|
2593
|
+
* attributes of a script tag.
|
|
2594
|
+
*/
|
|
2595
|
+
const CONSOLE_OPTION_NAMES = ['blockType', 'dividerOrient', 'hidePrefix', 'librariesUrl', 'runner', 'showResults', 'theme'];
|
|
2596
|
+
|
|
2597
|
+
/**
|
|
2598
|
+
* The JavaScript API for creating consoles (available as window.YourJSBox).
|
|
2599
|
+
*/
|
|
2600
|
+
const YourJSBox = Object.freeze({
|
|
2601
|
+
version: PACKAGE_INFO.version,
|
|
2602
|
+
|
|
2603
|
+
/**
|
|
2604
|
+
* Creates a console.
|
|
2605
|
+
* @param {Object} options
|
|
2606
|
+
* @param {string|Element} options.target
|
|
2607
|
+
* The element (or a CSS selector for it) that the console is placed
|
|
2608
|
+
* relative to.
|
|
2609
|
+
* @param {"fill"|"append"|"prepend"|"replace"|"before"|"after"=} options.placement
|
|
2610
|
+
* Optional, defaults to `"fill"`. Where the console goes: "fill"
|
|
2611
|
+
* replaces the target's contents, "append" and "prepend" add it inside
|
|
2612
|
+
* of the target, "replace" replaces the target itself and "before" and
|
|
2613
|
+
* "after" add it next to the target.
|
|
2614
|
+
* @param {(string|number)=} options.height
|
|
2615
|
+
* Optional, defaults to `"100%"`. The CSS height of the console (a
|
|
2616
|
+
* number is treated as pixels). It is never less than 150px.
|
|
2617
|
+
* @param {string=} options.code
|
|
2618
|
+
* Optional. The code that the console starts with.
|
|
2619
|
+
* @param {string=} options.runner
|
|
2620
|
+
* @param {string=} options.blockType
|
|
2621
|
+
* @param {(boolean|string)=} options.showResults
|
|
2622
|
+
* @param {string=} options.hidePrefix
|
|
2623
|
+
* @param {string=} options.dividerOrient
|
|
2624
|
+
* @param {string=} options.theme
|
|
2625
|
+
* @param {string=} options.librariesUrl
|
|
2626
|
+
* The same as the data attributes of a script tag.
|
|
2627
|
+
* @returns {{element: HTMLIFrameElement, destroy: () => void}}
|
|
2628
|
+
*/
|
|
2629
|
+
create(options) {
|
|
2630
|
+
options = Object(options);
|
|
2631
|
+
const {target, placement = 'fill', height, code = ''} = options;
|
|
2632
|
+
const targetElement = 'string' === typeof target ? document.querySelector(target) : target;
|
|
2633
|
+
if (targetElement?.nodeType !== 1) {
|
|
2634
|
+
throw new TypeError(
|
|
2635
|
+
'string' === typeof target
|
|
2636
|
+
? `YourJSBox.create(): no element matches the target ${JSON.stringify(target)}.`
|
|
2637
|
+
: 'YourJSBox.create(): target must be an element or a CSS selector.'
|
|
2638
|
+
);
|
|
2639
|
+
}
|
|
2640
|
+
if (!Object.hasOwn(PLACEMENTS, placement)) {
|
|
2641
|
+
throw new TypeError(`YourJSBox.create(): placement must be one of ${Object.keys(PLACEMENTS).join(', ')}.`);
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
const dataset = {};
|
|
2645
|
+
for (const name of CONSOLE_OPTION_NAMES) {
|
|
2646
|
+
if (options[name] != null) dataset[name] = `${options[name]}`;
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
return createConsole({
|
|
2650
|
+
code: `${code}`,
|
|
2651
|
+
dataset,
|
|
2652
|
+
height: 'number' === typeof height ? `${height}px` : height != null ? `${height}` : undefined,
|
|
2653
|
+
insert: element => PLACEMENTS[placement](targetElement, element),
|
|
2654
|
+
});
|
|
2655
|
+
},
|
|
2656
|
+
});
|
|
2657
|
+
|
|
2494
2658
|
// NOTE: This solution was intentionally written without using newer JS
|
|
2495
2659
|
// features to make the minified version even smaller.
|
|
2496
2660
|
var createCallableFrame = (function () {
|
|
@@ -2778,5 +2942,13 @@
|
|
|
2778
2942
|
return createCallableFrame;
|
|
2779
2943
|
})();
|
|
2780
2944
|
|
|
2781
|
-
|
|
2945
|
+
// The first copy of this script that is loaded provides the API.
|
|
2946
|
+
if (!window.YourJSBox) window.YourJSBox = YourJSBox;
|
|
2947
|
+
|
|
2948
|
+
// A script in the body is replaced by a console while a script in the head
|
|
2949
|
+
// only provides the API.
|
|
2950
|
+
const currentScript = document.currentScript;
|
|
2951
|
+
if (currentScript && !document.head?.contains(currentScript)) {
|
|
2952
|
+
createConsoleFromScript(currentScript);
|
|
2953
|
+
}
|
|
2782
2954
|
})();
|