Skip to content

Commit

Permalink
RELEASE: v2.34.0
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
tmedwards authored Jan 18, 2021
2 parents be5e586 + 652000c commit 36a8e16
Show file tree
Hide file tree
Showing 24 changed files with 178 additions and 80 deletions.
2 changes: 1 addition & 1 deletion dist/format.js

Large diffs are not rendered by default.

23 changes: 22 additions & 1 deletion docs/api/api-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
The `Config` object controls various aspects of SugarCube's behavior.

<p role="note"><b>Note:</b>
<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).
<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).
</p>


Expand Down Expand Up @@ -593,6 +593,27 @@ Config.saves.slots = 4;

<!-- *********************************************************************** -->

### `Config.saves.tryDiskOnMobile`*boolean* (default: `true`) {#config-api-property-saves-trydiskonmobile}

Determines whether saving to disk is enabled on mobile devices—i.e., smartphones, tablets, etc.

<p role="note" class="warning"><b>Warning:</b>
Mobile browsers can be fickle, so saving to disk may not work as expected in all browsers.
</p>

#### History:

* `v2.34.0`: Introduced.

#### Examples:

```
/* To disable saving to disk on mobile devices. */
Config.saves.tryDiskOnMobile = false;
```

<!-- *********************************************************************** -->

### `Config.saves.version`*any* (default: *none*) {#config-api-property-saves-version}

Sets the `version` property of saves.
Expand Down
4 changes: 4 additions & 0 deletions docs/api/api-setting.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

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

<p role="note" class="warning"><b>Warning:</b>
<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.
</p>

<!-- *********************************************************************** -->

### `Setting.addHeader(name [, desc])` {#setting-api-method-addheader}
Expand Down
2 changes: 1 addition & 1 deletion docs/api/api-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ State.metadata.set('ngplus', true);
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).
<p role="note"><b>Note:</b>
<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.
<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.
</p>
#### History:
Expand Down
4 changes: 2 additions & 2 deletions docs/core/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ Loading is done asynchronously at run time, so if the script must be available w
</p>

<p role="note"><b>Note:</b>
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>.
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>.
</p>

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

<p role="note"><b>Note:</b>
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>.
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>.
</p>

#### History:
Expand Down
6 changes: 3 additions & 3 deletions docs/core/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ The predefined variable <code>output</code>, which is a reference to a local con

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

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

<p role="note" class="tip"><b>Tip:</b>
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>.
Expand Down Expand Up @@ -315,7 +315,7 @@ You weigh <<= $weight.toFixed(2)>> kg. → Outputs: You weigh 74.65 kg.

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

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.
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.

<p role="note" class="tip"><b>Tip:</b>
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>.
Expand Down Expand Up @@ -403,7 +403,7 @@ cherry

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

Outputs the result of the given expression.
Outputs a string representation of the result of the given expression.

<p role="note" class="tip"><b>Tip:</b>
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>.
Expand Down
2 changes: 1 addition & 1 deletion docs/core/markup.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Except where noted, all markup has been available since <code>v2.0.0</code>.
**************************************************************************** -->
## Naked Variable {#markup-naked-variable}

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.
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.

The following forms are supported by the naked variable markup:

Expand Down
4 changes: 4 additions & 0 deletions docs/core/twinescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ The following types of values are natively supported by SugarCube and may be saf

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

<p role="note" class="warning"><b>Warning:</b>
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).
</p>

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.


Expand Down
2 changes: 1 addition & 1 deletion docs/guides/guide-localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ In use, replacement patterns are replaced recursively, so replacement strings ma
**************************************************************************** -->
## Usage {#guide-localization-usage}

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.
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.

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/).

Expand Down
6 changes: 5 additions & 1 deletion docs/guides/guide-tips.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Due to how the Twine&nbsp;2 automatic passage creation feature currently works,

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.

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.
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.

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.

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

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.

<p role="note" class="see"><b>See Also:</b>
The <a href="#methods-json-method-revivewrapper"><code>JSON.reviveWrapper()</code> method</a> for additional information on implementing the <code>.toJSON()</code> method.
</p>

#### Examples: *(not an exhaustive list)*

##### Config/option object parameter constructor (automatic copying of own data)
Expand Down
3 changes: 2 additions & 1 deletion docs/table-of-contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@
* [`<<cacheaudio>>`](#macros-macro-cacheaudio)
* [`<<createaudiogroup>>`](#macros-macro-createaudiogroup)
* [`<<createplaylist>>`](#macros-macro-createplaylist)
* [`<<playlist>>`](#macros-macro-playlist)
* [`<<masteraudio>>`](#macros-macro-masteraudio)
* [`<<playlist>>`](#macros-macro-playlist)
* [`<<removeaudiogroup>>`](#macros-macro-removeaudiogroup)
* [`<<removeplaylist>>`](#macros-macro-removeplaylist)
* [`<<waitforaudio>>`](#macros-macro-waitforaudio)
Expand Down Expand Up @@ -297,6 +297,7 @@
* [`Config.saves.onLoad`](#config-api-property-saves-onload)
* [`Config.saves.onSave`](#config-api-property-saves-onsave)
* [`Config.saves.slots`](#config-api-property-saves-slots)
* [`Config.saves.tryDiskOnMobile`](#config-api-property-saves-trydiskonmobile)
* [`Config.saves.version`](#config-api-property-saves-version)
* [UI Settings](#config-api-ui)
* [`Config.ui.stowBarInitially`](#config-api-property-ui-stowbarinitially)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "SugarCube",
"version": "2.33.4",
"version": "2.34.0",
"author": "Thomas Michael Edwards <[email protected]>",
"description": "Dependency install configuration for SugarCube's Node.js-hosted build script, build.js.",
"license": "BSD-2-Clause",
Expand Down
8 changes: 6 additions & 2 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ var Config = (() => { // eslint-disable-line no-unused-vars, no-var
// Saves settings.
let _savesAutoload;
let _savesAutosave;
let _savesId = 'untitled-story';
let _savesId = 'untitled-story';
let _savesIsAllowed;
let _savesOnLoad;
let _savesOnSave;
let _savesSlots = 8;
let _savesSlots = 8;
let _savesTryDiskOnMobile = true;
let _savesVersion;

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

get tryDiskOnMobile() { return _savesTryDiskOnMobile; },
set tryDiskOnMobile(value) { _savesTryDiskOnMobile = Boolean(value); },

get version() { return _savesVersion; },
set version(value) { _savesVersion = value; }
}),
Expand Down
36 changes: 36 additions & 0 deletions src/lib/extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,42 @@
}
});

/*
Backup the original `JSON.stringify()` and replace it with a revive wrapper aware version.
*/
Object.defineProperty(JSON, '_real_stringify', {
value : JSON.stringify
});
Object.defineProperty(JSON, 'stringify', {
configurable : true,
writable : true,

value(text, replacer, space) {
return JSON._real_stringify(text, (key, val) => {
let value = val;

/*
Call the custom replacer, if specified.
*/
if (typeof replacer === 'function') {
try {
value = replacer(key, value);
}
catch (ex) { /* no-op; although, perhaps, it would be better to throw an error here */ }
}

/*
Attempt to replace values.
*/
if (typeof value === 'undefined') {
value = ['(revive:eval)', 'undefined'];
}

return value;
}, space);
}
});

/*
Backup the original `JSON.parse()` and replace it with a revive wrapper aware version.
*/
Expand Down
1 change: 0 additions & 1 deletion src/lib/has.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ var Has = (() => { // eslint-disable-line no-unused-vars, no-var
'File' in window &&
'FileList' in window &&
'FileReader' in window &&
!Browser.isMobile.any() &&
(!Browser.isOpera || Browser.operaVersion >= 15);
}
catch (ex) { /* no-op */ }
Expand Down
44 changes: 28 additions & 16 deletions src/lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var { // eslint-disable-line no-var
setDisplayTitle,
setPageElement,
throwError,
toStringOrDefault
stringFrom
/* eslint-enable no-unused-vars */
} = (() => {
'use strict';
Expand Down Expand Up @@ -356,45 +356,57 @@ var { // eslint-disable-line no-var
}

/*
Returns the simple string representation of the passed value or, if there is none,
the passed default value.
Returns the simple string representation of the given value or, if there is
none, a square bracketed representation.
*/
function toStringOrDefault(value, defValue) {
const tSOD = toStringOrDefault;

function stringFrom(value) {
switch (typeof value) {
case 'function':
return '[function]';

case 'number':
// TODO: Perhaps NaN should be printed instead?
if (Number.isNaN(value)) {
return defValue;
return '[number NaN]';
}

break;

case 'object':
if (value === null) {
return defValue;
return '[null]';
}
else if (Array.isArray(value)) {
return value.map(val => tSOD(val, defValue)).join(', ');
else if (value instanceof Array) {
return value.map(val => stringFrom(val)).join(', ');
}
else if (value instanceof Set) {
return [...value].map(val => tSOD(val, defValue)).join(', ');
return Array.from(value).map(val => stringFrom(val)).join(', ');
}
else if (value instanceof Map) {
const result = [...value].map(([key, val]) => `${tSOD(key, defValue)} \u2192 ${tSOD(val, defValue)}`);
const result = Array.from(value).map(([key, val]) => `${stringFrom(key)} \u2192 ${stringFrom(val)}`);
return `{\u202F${result.join(', ')}\u202F}`;
}
else if (value instanceof Date) {
return value.toLocaleString();
}
else if (value instanceof Element) {
return value.outerHTML;
}
else if (value instanceof Node) {
return value.textContent;
}
else if (typeof value.toString === 'function') {
return value.toString();
}

return Object.prototype.toString.call(value);

case 'function':
case 'symbol': {
const desc = typeof value.description !== 'undefined' ? ` "${value.description}"` : '';
return `[symbol${desc}]`;
}

case 'undefined':
return defValue;
return '[undefined]';
}

return String(value);
Expand All @@ -411,6 +423,6 @@ var { // eslint-disable-line no-var
setDisplayTitle : { value : setDisplayTitle },
setPageElement : { value : setPageElement },
throwError : { value : throwError },
toStringOrDefault : { value : toStringOrDefault }
stringFrom : { value : stringFrom }
}));
})();
Loading

0 comments on commit 36a8e16

Please sign in to comment.