Skip to content

Commit 36a8e16

Browse files
authored
RELEASE: v2.34.0
## Changelog: * Fixed the `<AudioRunner>.fadeIn()` and `<AudioRunner>.fadeOut()` methods. * Fixed explicitly set `undefined` values within arrays being transformed into `null` when roundtripping through sessions/saves. * Fixed `<<type>>` to immediately start typing, after any start delay, rather than also waiting one typing speed delay. * Updated saves to support saving to disk on mobile devices, by default—see the `Config.saves.tryDiskOnMobile` setting if you wish to disable it. * Updated the naked variable markup and `<<print>>` family of macros to: include default conversions for DOM objects and better conversions for various edge cases values. * Updated the documentation—mostly a few minor edits here and there. * Updated bundled library: `FileSaver.js` to v2.0.4.
2 parents be5e586 + 652000c commit 36a8e16

24 files changed

+178
-80
lines changed

dist/format.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/api/api-config.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
The `Config` object controls various aspects of SugarCube's behavior.
77

88
<p role="note"><b>Note:</b>
9-
<code>Config</code> object settings should be placed within a script section (Twine&nbsp;2: the Story JavaScript; Twine&nbsp;1/Twee: a <code>script</code>-tagged passage).
9+
<code>Config</code> object settings should be placed within your project's JavaScript section (Twine&nbsp;2: the Story JavaScript; Twine&nbsp;1/Twee: a <code>script</code>-tagged passage).
1010
</p>
1111

1212

@@ -593,6 +593,27 @@ Config.saves.slots = 4;
593593

594594
<!-- *********************************************************************** -->
595595

596+
### `Config.saves.tryDiskOnMobile`*boolean* (default: `true`) {#config-api-property-saves-trydiskonmobile}
597+
598+
Determines whether saving to disk is enabled on mobile devices—i.e., smartphones, tablets, etc.
599+
600+
<p role="note" class="warning"><b>Warning:</b>
601+
Mobile browsers can be fickle, so saving to disk may not work as expected in all browsers.
602+
</p>
603+
604+
#### History:
605+
606+
* `v2.34.0`: Introduced.
607+
608+
#### Examples:
609+
610+
```
611+
/* To disable saving to disk on mobile devices. */
612+
Config.saves.tryDiskOnMobile = false;
613+
```
614+
615+
<!-- *********************************************************************** -->
616+
596617
### `Config.saves.version`*any* (default: *none*) {#config-api-property-saves-version}
597618

598619
Sets the `version` property of saves.

docs/api/api-setting.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
Manages the Settings dialog and [`settings` object](#setting-api-object-settings).
77

8+
<p role="note" class="warning"><b>Warning:</b>
9+
<code>Setting</code> API method calls <strong><em>must</em></strong> be placed within your project's JavaScript section (Twine&nbsp;2: the Story JavaScript; Twine&nbsp;1/Twee: a <code>script</code>-tagged passage) or settings will not function correctly.
10+
</p>
11+
812
<!-- *********************************************************************** -->
913

1014
### `Setting.addHeader(name [, desc])` {#setting-api-method-addheader}

docs/api/api-state.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ State.metadata.set('ngplus', true);
473473
Initializes the seedable pseudo-random number generator (PRNG) and integrates it into the story state and saves. Once initialized, the [`State.random()`](#state-api-method-random) method and story functions, [`random()`](#functions-function-random) and [`randomFloat()`](#functions-function-randomfloat), return deterministic results from the seeded PRNG—by default, they return non-deterministic results from [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random).
474474
475475
<p role="note"><b>Note:</b>
476-
<code>State.prng.init()</code> <em>must be</em> called during story initialization, within either a script section (Twine&nbsp;2: the Story JavaScript, Twine&nbsp;1/Twee: a <code>script</code>-tagged passage) or the <code>StoryInit</code> special passage. Additionally, it is <strong><em>strongly</em></strong> recommended that you do not specify any arguments to <code>State.prng.init()</code> and allow it to automatically seed itself. If you should chose to use an explicit seed, however, it is <strong><em>strongly</em></strong> recommended that you also enable additional entropy, otherwise all playthroughs for all players will be exactly the same.
476+
<code>State.prng.init()</code> <em>must be</em> called during story initialization, within either your project's JavaScript section (Twine&nbsp;2: the Story JavaScript; Twine&nbsp;1/Twee: a <code>script</code>-tagged passage) or the <code>StoryInit</code> special passage. Additionally, it is <strong><em>strongly</em></strong> recommended that you do not specify any arguments to <code>State.prng.init()</code> and allow it to automatically seed itself. If you should chose to use an explicit seed, however, it is <strong><em>strongly</em></strong> recommended that you also enable additional entropy, otherwise all playthroughs for all players will be exactly the same.
477477
</p>
478478
479479
#### History:

docs/core/functions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Loading is done asynchronously at run time, so if the script must be available w
149149
</p>
150150

151151
<p role="note"><b>Note:</b>
152-
A script section (Twine&nbsp;2: the Story JavaScript; Twine&nbsp;1/Twee: a <code>script</code>-tagged passage) is normally the best place to call <code>importScripts()</code>.
152+
Your project's JavaScript section (Twine&nbsp;2: the Story JavaScript; Twine&nbsp;1/Twee: a <code>script</code>-tagged passage) is normally the best place to call <code>importScripts()</code>.
153153
</p>
154154

155155
#### History:
@@ -237,7 +237,7 @@ Loading is done asynchronously at run time, so if the stylesheet must be availab
237237
</p>
238238

239239
<p role="note"><b>Note:</b>
240-
A script section (Twine&nbsp;2: the Story JavaScript; Twine&nbsp;1/Twee: a <code>script</code>-tagged passage) is normally the best place to call <code>importStyles()</code>.
240+
Your project's JavaScript section (Twine&nbsp;2: the Story JavaScript; Twine&nbsp;1/Twee: a <code>script</code>-tagged passage) is normally the best place to call <code>importStyles()</code>.
241241
</p>
242242

243243
#### History:

docs/core/macros.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ The predefined variable <code>output</code>, which is a reference to a local con
287287

288288
### `<<= expression>>` {#macros-macro-equal}
289289

290-
Outputs the result of the given expression. This macro is an alias for [`<<print>>`](#macros-macro-print).
290+
Outputs a string representation of the result of the given expression. This macro is an alias for [`<<print>>`](#macros-macro-print).
291291

292292
<p role="note" class="tip"><b>Tip:</b>
293293
If you only need to print the value of a TwineScript variable, then you may simply include it in your normal passage text and it will be printed automatically via the <a href="#markup-naked-variable">naked variable markup</a>.
@@ -315,7 +315,7 @@ You weigh <<= $weight.toFixed(2)>> kg. → Outputs: You weigh 74.65 kg.
315315

316316
### `<<- expression>>` {#macros-macro-hyphen}
317317

318-
Outputs the result of the given expression. This macro is functionally identical to [`<<print>>`](#macros-macro-print), save that it also encodes HTML special characters in the output.
318+
Outputs a string representation of the result of the given expression. This macro is functionally identical to [`<<print>>`](#macros-macro-print), save that it also encodes HTML special characters in the output.
319319

320320
<p role="note" class="tip"><b>Tip:</b>
321321
If you only need to print the value of a TwineScript variable, then you may simply include it in your normal passage text and it will be printed automatically via the <a href="#markup-naked-variable">naked variable markup</a>.
@@ -403,7 +403,7 @@ cherry
403403

404404
### `<<print expression>>` {#macros-macro-print}
405405

406-
Outputs the result of the given expression.
406+
Outputs a string representation of the result of the given expression.
407407

408408
<p role="note" class="tip"><b>Tip:</b>
409409
If you only need to print the value of a TwineScript variable, then you may simply include it in your normal passage text and it will be printed automatically via the <a href="#markup-naked-variable">naked variable markup</a>.

docs/core/markup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Except where noted, all markup has been available since <code>v2.0.0</code>.
1313
**************************************************************************** -->
1414
## Naked Variable {#markup-naked-variable}
1515

16-
In addition to using one of the print macros ([`<<print>>`](#macros-macro-print), [`<<=>>`](#macros-macro-equal), [`<<->>`](#macros-macro-hyphen)) to print the values of TwineScript variables, SugarCube's naked variable markup allows printing them simply by including them within your normal passage text—i.e., variables in passage text are interpolated into their values.
16+
In addition to using one of the print macros ([`<<print>>`](#macros-macro-print), [`<<=>>`](#macros-macro-equal), [`<<->>`](#macros-macro-hyphen)) to print the values of TwineScript variables, SugarCube's naked variable markup allows printing them simply by including them within your normal passage text—i.e., variables in passage text are interpolated into a string representation of their values.
1717

1818
The following forms are supported by the naked variable markup:
1919

docs/core/twinescript.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ The following types of values are natively supported by SugarCube and may be saf
8585

8686
Any supported object type may itself contain any supported primitive or object type.
8787

88+
<p role="note" class="warning"><b>Warning:</b>
89+
Neither ES5 property attributes—which includes getters/setters—nor symbol properties are directly supported in generic objects stored within story variables. If you need such features, then you'll need to use a non-generic object (a.k.a. a class).
90+
</p>
91+
8892
Unsupported object types, either native or custom, can be made compatible by implementing `.clone()` and `.toJSON()` methods for them—see the [*Non-generic object types (a.k.a. classes)* guide](#guide-tips-non-generic-object-types) for more information.
8993

9094

docs/guides/guide-localization.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ In use, replacement patterns are replaced recursively, so replacement strings ma
4040
**************************************************************************** -->
4141
## Usage {#guide-localization-usage}
4242

43-
Properties on the strings localization object (`l10nStrings`) may be set within your project's script section (Twine&nbsp;2: the Story JavaScript, Twine&nbsp;1/Twee: a `script`-tagged passage) to override the defaults.
43+
Properties on the strings localization object (`l10nStrings`) should be set within your project's JavaScript section (Twine&nbsp;2: the Story JavaScript; Twine&nbsp;1/Twee: a <code>script</code>-tagged passage) to override the defaults.
4444

4545
For the template that should be used as the basis of localizations, see the [`locale/l10n-template.js` file @github.com](https://github.com/tmedwards/sugarcube-2/tree/develop/locale/).
4646

docs/guides/guide-tips.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Due to how the Twine&nbsp;2 automatic passage creation feature currently works,
5454

5555
As a basic working definition, non-generic object types—a.k.a. classes—are instantiable objects whose own prototype is not `Object`—e.g., `Array` is a native non-generic object type.
5656

57-
Most of the commonly used native non-generic object types are already fully compatible with and supported for use within story variables—e.g., `Array`, `Date`, `Map`, and `Set`. Non-native/custom non-generic object types, on the other hand, must be made compatible to be successfully stored within story variables.
57+
Many of the commonly used native non-generic object types are already fully compatible with and supported for use within story variables—e.g., `Array`, `Date`, `Map`, and `Set`. All other non-generic object types, on the other hand, must be made compatible to be successfully stored within story variables.
5858

5959
Making custom non-generic object types fully compatible requires that two methods be added to their prototype, `.clone()` and `.toJSON()`, to support cloning—i.e., deep copying—instances of the type.
6060

@@ -63,6 +63,10 @@ Making custom non-generic object types fully compatible requires that two method
6363

6464
In both cases, since the end goal is roughly the same, this means creating a new instance of the base object type and populating it with clones of the original instance's data. There is no one size fits all example for either of these methods because an instance's properties, and the data contained therein, are what determine what you need to do.
6565

66+
<p role="note" class="see"><b>See Also:</b>
67+
The <a href="#methods-json-method-revivewrapper"><code>JSON.reviveWrapper()</code> method</a> for additional information on implementing the <code>.toJSON()</code> method.
68+
</p>
69+
6670
#### Examples: *(not an exhaustive list)*
6771

6872
##### Config/option object parameter constructor (automatic copying of own data)

docs/table-of-contents.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@
9292
* [`<<cacheaudio>>`](#macros-macro-cacheaudio)
9393
* [`<<createaudiogroup>>`](#macros-macro-createaudiogroup)
9494
* [`<<createplaylist>>`](#macros-macro-createplaylist)
95-
* [`<<playlist>>`](#macros-macro-playlist)
9695
* [`<<masteraudio>>`](#macros-macro-masteraudio)
96+
* [`<<playlist>>`](#macros-macro-playlist)
9797
* [`<<removeaudiogroup>>`](#macros-macro-removeaudiogroup)
9898
* [`<<removeplaylist>>`](#macros-macro-removeplaylist)
9999
* [`<<waitforaudio>>`](#macros-macro-waitforaudio)
@@ -297,6 +297,7 @@
297297
* [`Config.saves.onLoad`](#config-api-property-saves-onload)
298298
* [`Config.saves.onSave`](#config-api-property-saves-onsave)
299299
* [`Config.saves.slots`](#config-api-property-saves-slots)
300+
* [`Config.saves.tryDiskOnMobile`](#config-api-property-saves-trydiskonmobile)
300301
* [`Config.saves.version`](#config-api-property-saves-version)
301302
* [UI Settings](#config-api-ui)
302303
* [`Config.ui.stowBarInitially`](#config-api-property-ui-stowbarinitially)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "SugarCube",
3-
"version": "2.33.4",
3+
"version": "2.34.0",
44
"author": "Thomas Michael Edwards <thomasmedwards@gmail.com>",
55
"description": "Dependency install configuration for SugarCube's Node.js-hosted build script, build.js.",
66
"license": "BSD-2-Clause",

src/config.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ var Config = (() => { // eslint-disable-line no-unused-vars, no-var
4545
// Saves settings.
4646
let _savesAutoload;
4747
let _savesAutosave;
48-
let _savesId = 'untitled-story';
48+
let _savesId = 'untitled-story';
4949
let _savesIsAllowed;
5050
let _savesOnLoad;
5151
let _savesOnSave;
52-
let _savesSlots = 8;
52+
let _savesSlots = 8;
53+
let _savesTryDiskOnMobile = true;
5354
let _savesVersion;
5455

5556
// UI settings.
@@ -334,6 +335,9 @@ var Config = (() => { // eslint-disable-line no-unused-vars, no-var
334335
_savesSlots = value;
335336
},
336337

338+
get tryDiskOnMobile() { return _savesTryDiskOnMobile; },
339+
set tryDiskOnMobile(value) { _savesTryDiskOnMobile = Boolean(value); },
340+
337341
get version() { return _savesVersion; },
338342
set version(value) { _savesVersion = value; }
339343
}),

src/lib/extensions.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,42 @@
16771677
}
16781678
});
16791679

1680+
/*
1681+
Backup the original `JSON.stringify()` and replace it with a revive wrapper aware version.
1682+
*/
1683+
Object.defineProperty(JSON, '_real_stringify', {
1684+
value : JSON.stringify
1685+
});
1686+
Object.defineProperty(JSON, 'stringify', {
1687+
configurable : true,
1688+
writable : true,
1689+
1690+
value(text, replacer, space) {
1691+
return JSON._real_stringify(text, (key, val) => {
1692+
let value = val;
1693+
1694+
/*
1695+
Call the custom replacer, if specified.
1696+
*/
1697+
if (typeof replacer === 'function') {
1698+
try {
1699+
value = replacer(key, value);
1700+
}
1701+
catch (ex) { /* no-op; although, perhaps, it would be better to throw an error here */ }
1702+
}
1703+
1704+
/*
1705+
Attempt to replace values.
1706+
*/
1707+
if (typeof value === 'undefined') {
1708+
value = ['(revive:eval)', 'undefined'];
1709+
}
1710+
1711+
return value;
1712+
}, space);
1713+
}
1714+
});
1715+
16801716
/*
16811717
Backup the original `JSON.parse()` and replace it with a revive wrapper aware version.
16821718
*/

src/lib/has.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ var Has = (() => { // eslint-disable-line no-unused-vars, no-var
3333
'File' in window &&
3434
'FileList' in window &&
3535
'FileReader' in window &&
36-
!Browser.isMobile.any() &&
3736
(!Browser.isOpera || Browser.operaVersion >= 15);
3837
}
3938
catch (ex) { /* no-op */ }

src/lib/helpers.js

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var { // eslint-disable-line no-var
1616
setDisplayTitle,
1717
setPageElement,
1818
throwError,
19-
toStringOrDefault
19+
stringFrom
2020
/* eslint-enable no-unused-vars */
2121
} = (() => {
2222
'use strict';
@@ -356,45 +356,57 @@ var { // eslint-disable-line no-var
356356
}
357357

358358
/*
359-
Returns the simple string representation of the passed value or, if there is none,
360-
the passed default value.
359+
Returns the simple string representation of the given value or, if there is
360+
none, a square bracketed representation.
361361
*/
362-
function toStringOrDefault(value, defValue) {
363-
const tSOD = toStringOrDefault;
364-
362+
function stringFrom(value) {
365363
switch (typeof value) {
364+
case 'function':
365+
return '[function]';
366+
366367
case 'number':
367-
// TODO: Perhaps NaN should be printed instead?
368368
if (Number.isNaN(value)) {
369-
return defValue;
369+
return '[number NaN]';
370370
}
371+
371372
break;
372373

373374
case 'object':
374375
if (value === null) {
375-
return defValue;
376+
return '[null]';
376377
}
377-
else if (Array.isArray(value)) {
378-
return value.map(val => tSOD(val, defValue)).join(', ');
378+
else if (value instanceof Array) {
379+
return value.map(val => stringFrom(val)).join(', ');
379380
}
380381
else if (value instanceof Set) {
381-
return [...value].map(val => tSOD(val, defValue)).join(', ');
382+
return Array.from(value).map(val => stringFrom(val)).join(', ');
382383
}
383384
else if (value instanceof Map) {
384-
const result = [...value].map(([key, val]) => `${tSOD(key, defValue)} \u2192 ${tSOD(val, defValue)}`);
385+
const result = Array.from(value).map(([key, val]) => `${stringFrom(key)} \u2192 ${stringFrom(val)}`);
385386
return `{\u202F${result.join(', ')}\u202F}`;
386387
}
387388
else if (value instanceof Date) {
388389
return value.toLocaleString();
389390
}
391+
else if (value instanceof Element) {
392+
return value.outerHTML;
393+
}
394+
else if (value instanceof Node) {
395+
return value.textContent;
396+
}
390397
else if (typeof value.toString === 'function') {
391398
return value.toString();
392399
}
400+
393401
return Object.prototype.toString.call(value);
394402

395-
case 'function':
403+
case 'symbol': {
404+
const desc = typeof value.description !== 'undefined' ? ` "${value.description}"` : '';
405+
return `[symbol${desc}]`;
406+
}
407+
396408
case 'undefined':
397-
return defValue;
409+
return '[undefined]';
398410
}
399411

400412
return String(value);
@@ -411,6 +423,6 @@ var { // eslint-disable-line no-var
411423
setDisplayTitle : { value : setDisplayTitle },
412424
setPageElement : { value : setPageElement },
413425
throwError : { value : throwError },
414-
toStringOrDefault : { value : toStringOrDefault }
426+
stringFrom : { value : stringFrom }
415427
}));
416428
})();

0 commit comments

Comments
 (0)