diff --git a/build.js b/build.js index 4d90705..661ddb2 100644 --- a/build.js +++ b/build.js @@ -1,15 +1,14 @@ #!/usr/bin/env node /*********************************************************************************************************************** - build.js (v1.4.18, 2020-11-08) + build.js (v1.6.0, 2021-12-19) A Node.js-hosted build script for SugarCube. - Copyright © 2013–2020 Thomas Michael Edwards . All rights reserved. + Copyright © 2013–2021 Thomas Michael Edwards . All rights reserved. Use of this source code is governed by a BSD 2-clause "Simplified" License, which may be found in the LICENSE file. ***********************************************************************************************************************/ /* eslint-env node, es6 */ -/* eslint-disable strict */ 'use strict'; /******************************************************************************* @@ -142,11 +141,18 @@ const CONFIG = { the replacement strings (e.g. '$&' within the application source). */ -const _fs = require('fs'); +const { + log, + die, + fileExists, + makePath, + copyFile, + readFileContents, + writeFileContents, + concatFiles +} = require('./scripts/build-utils'); const _path = require('path'); - -const _indent = ' -> '; -const _opt = require('node-getopt').create([ +const _opt = require('node-getopt').create([ ['b', 'build=VERSION', 'Build only for Twine major version: 1 or 2; default: build for all.'], ['d', 'debug', 'Keep debugging code; gated by DEBUG symbol.'], ['u', 'unminified', 'Suppress minification stages.'], @@ -186,7 +192,7 @@ if (_opt.options.build) { console.log('Starting builds...'); // Create the build ID file, if nonexistent. - if (!_fs.existsSync('.build')) { + if (!fileExists('.build')) { writeFileContents('.build', '0'); } @@ -203,7 +209,7 @@ if (_opt.options.build) { patch : semver.patch(version), prerelease : prerelease && prerelease.length > 0 ? prerelease.join('.') : null, build : Number(readFileContents('.build')) + 1, - date : (new Date()).toISOString(), + date : new Date().toISOString(), toString() { const prerelease = this.prerelease ? `-${this.prerelease}` : ''; @@ -277,93 +283,6 @@ console.log('\nBuilds complete! (check the "build" directory)'); /******************************************************************************* Utility Functions *******************************************************************************/ -function log(message, indent) { - console.log('%s%s', indent ? indent : _indent, message); -} - -// function warn(message) { -// console.warn('%swarning: %s', _indent, message); -// } - -function die(message, error) { - if (error) { - console.error('error: %s\n[@: %d/%d] Trace:\n', message, error.line, error.col, error.stack); - } - else { - console.error('error: %s', message); - } - - process.exit(1); -} - -function makePath(pathname) { - const pathBits = _path.normalize(pathname).split(_path.sep); - - for (let i = 0; i < pathBits.length; ++i) { - const dirPath = i === 0 ? pathBits[i] : pathBits.slice(0, i + 1).join(_path.sep); - - if (!_fs.existsSync(dirPath)) { - _fs.mkdirSync(dirPath); - } - } -} - -function copyFile(srcFilename, destFilename) { - const srcPath = _path.normalize(srcFilename); - const destPath = _path.normalize(destFilename); - let buf; - - try { - buf = _fs.readFileSync(srcPath); - } - catch (ex) { - die(`cannot open file "${srcPath}" for reading (reason: ${ex.message})`); - } - - try { - _fs.writeFileSync(destPath, buf); - } - catch (ex) { - die(`cannot open file "${destPath}" for writing (reason: ${ex.message})`); - } - - return true; -} - -function readFileContents(filename) { - const filepath = _path.normalize(filename); - - try { - // the replace() is necessary because Node.js only offers binary mode file - // access, regardless of platform, so we convert DOS-style line terminators - // to UNIX-style, just in case someone adds/edits a file and gets DOS-style - // line termination all over it - return _fs.readFileSync(filepath, { encoding : 'utf8' }).replace(/\r\n/g, '\n'); - } - catch (ex) { - die(`cannot open file "${filepath}" for reading (reason: ${ex.message})`); - } -} - -function writeFileContents(filename, data) { - const filepath = _path.normalize(filename); - - try { - _fs.writeFileSync(filepath, data, { encoding : 'utf8' }); - } - catch (ex) { - die(`cannot open file "${filepath}" for writing (reason: ${ex.message})`); - } -} - -function concatFiles(filenames, callback) { - const output = filenames.map(filename => { - const contents = readFileContents(filename); - return typeof callback === 'function' ? callback(contents, filename) : contents; - }); - return output.join('\n'); -} - function assembleLibraries(filenames) { log('assembling libraries...'); @@ -397,37 +316,24 @@ function compileJavaScript(filenameObj, options) { ].join(';\n') + ';\n' + jsSource; } - try { - const uglifyjs = require('uglify-js'); - const uglified = uglifyjs.minify(jsSource, { - fromString : true, - compress : { - global_defs : { - TWINE1 : !!options.twine1, - DEBUG : _opt.options.debug || false - }, - screw_ie8 : true - }, - mangle : { - screw_ie8 : true + const uglifyjs = require('uglify-js'); + const minified = uglifyjs.minify(jsSource, { + compress : { + global_defs : { + TWINE1 : !!options.twine1, + DEBUG : _opt.options.debug || false }, - output : { - screw_ie8 : true - } - }); - return uglified.code; - } - catch (ex) { - let mesg = 'uglification error'; - - if (ex.line > 0) { - const begin = ex.line > 4 ? ex.line - 4 : 0; - const end = ex.line + 3 < jsSource.length ? ex.line + 3 : jsSource.length; - mesg += ':\n >> ' + jsSource.split(/\n/).slice(begin, end).join('\n >> '); - } + keep_infinity : true + }, + mangle : false + }); - die(mesg, ex); + if (minified.error) { + const { message, line, col, pos } = minified.error; + die(`JavaScript minification error: ${message}\n[@: ${line}/${col}/${pos}]`); } + + return minified.code; /* eslint-enable camelcase, prefer-template */ } diff --git a/dist/format.js b/dist/format.js index 376379c..6407157 100644 --- a/dist/format.js +++ b/dist/format.js @@ -1 +1 @@ -window.storyFormat({"name":"SugarCube","version":"2.35.0","description":"A full featured, highly customizable story format. See its documentation.","author":"Thomas Michael Edwards","image":"icon.svg","url":"http://www.motoslave.net/sugarcube/","license":"BSD-2-Clause","proofing":false,"source":"\n\n\n\n{{STORY_NAME}}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t
\n\t\t
\n\t\t
Your browser lacks required capabilities. Please upgrade it or switch to another to continue.
\n\t\t
Loading…
\n\t
\n\t{{STORY_DATA}}\n\t\n\n\n"}); \ No newline at end of file +window.storyFormat({"name":"SugarCube","version":"2.36.0","description":"A full featured, highly customizable story format. See its documentation.","author":"Thomas Michael Edwards","image":"icon.svg","url":"http://www.motoslave.net/sugarcube/","license":"BSD-2-Clause","proofing":false,"source":"\n\n\n\n{{STORY_NAME}}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t
\n\t\t
\n\t\t

Browser lacks capabilities required to play.

Upgrade or switch to another browser.

\n\t\t
Loading…
\n\t
\n\t{{STORY_DATA}}\n\t\n\n\n"}); \ No newline at end of file diff --git a/docs/api/api-config.md b/docs/api/api-config.md index 3a00809..59ab305 100644 --- a/docs/api/api-config.md +++ b/docs/api/api-config.md @@ -75,25 +75,27 @@ Config.history.controls = false; -### `Config.history.maxStates` ↔ *integer* (default: `100`) {#config-api-property-history-maxstates} +### `Config.history.maxStates` ↔ *integer* (default: `40`) {#config-api-property-history-maxstates} -Sets the maximum number of states (moments) to which the history is allowed to grow. Should the history exceed the limit, states will be dropped from the past (oldest first). A setting of `0` means that there is no limit to how large the history may grow, though doing so is not recommended. +Sets the maximum number of states (moments) to which the history is allowed to grow. Should the history exceed the limit, states will be dropped from the past (oldest first). + +

Tip: +For game-oriented projects, as opposed to more story-oriented interactive fiction, a setting of 1 is strongly recommended. +

#### History: * `v2.0.0`: Introduced. +* `v2.36.0`: Reduced the default to `40`. #### Examples: ``` -// No history limit (you should never do this!) -Config.history.maxStates = 0; - -// Limit the history to a single state +// Limit the history to a single state (recommended for games) Config.history.maxStates = 1; -// Limit the history to 150 states -Config.history.maxStates = 150; +// Limit the history to 80 states +Config.history.maxStates = 80; ``` @@ -426,9 +428,16 @@ Config.saves.autoload = function () { -### `Config.saves.autosave` ↔ *boolean* | *string array* | *function* (default: *none*) {#config-api-property-saves-autosave} +### `Config.saves.autosave` ↔ *boolean* | *Array<string>* | *function* (default: *none*) {#config-api-property-saves-autosave} + +Determines whether the autosave is created/updated when passages are displayed. + +Valid values are: -Determines whether the autosave is created/updated when passages are displayed. Valid values are boolean `true`, which causes the autosave to be updated with every passage, an array of strings, which causes the autosave to be updated for each passage with at least one matching tag, or a function, which causes the autosave to be updated for each passage where its return value is truthy. +* Boolean `true`, which causes the autosave to be updated with every passage. +* Boolean `false`, which causes the autosave to never update automatically—i.e., you must do it manually via the Save.autosave.save() static method. +* An array of strings, which causes the autosave to be updated for each passage with at least one matching tag. +* A function, which causes the autosave to be updated for each passage where its return value is truthy.

Warning: When setting the value to boolean true, you will likely also need to use the Config.saves.isAllowed property to disallow saving on the start passage. Or, if you use the start passage as real part of your story and allow the player to reenter it, rather than just as the initial landing/cover page, then you may wish to only disallow saving on the start passage the very first time it's displayed—i.e., at story startup. @@ -445,6 +454,9 @@ When setting the value to boolean true, you will likely also need t // Autosaves every passage Config.saves.autosave = true; +// Allows manual autosaving +Config.saves.autosave = false; + // Autosaves on passages tagged with any of "bookmark" or "autosave" Config.saves.autosave = ["bookmark", "autosave"]; @@ -490,93 +502,6 @@ Config.saves.isAllowed = function () { -### `Config.saves.onLoad` ↔ *function* (default: *none*) {#config-api-property-saves-onload} - -Performs any required pre-processing before the save data is loaded—e.g., upgrading out-of-date save data. The callback is passed one parameter, the save object to be processed. If it encounters an unrecoverable problem during its processing, it may throw an exception containing an error message; the message will be displayed to the player and loading of the save will be terminated. - -#### History: - -* `v2.0.0`: Introduced. - -#### Callback parameters: - -* **`save`:** (*object*) The save object to be pre-processed. - -#### Save object: - -

Note: -See the save objects section of the Save API for information on the format of a save. -

- -#### Examples: - -``` -Config.saves.onLoad = function (save) { - /* code to pre-process the save object */ -}; -``` - - - -### `Config.saves.onSave` ↔ *function* (default: *none*) {#config-api-property-saves-onsave} - -Performs any required post-processing before the save data is saved. The callback is passed two parameters, the save object to be processed and save operation details object. - -#### History: - -* `v2.0.0`: Introduced. -* `v2.33.0`: Added save operation details object parameter to the callback function. - -#### Callback parameters: - -* **`save`:** (*object*) The save object to be post-processed. -* **`details`:** (*object*) The save operation details object. - -#### Save object: - -

Note: -See the save objects section of the Save API for information on the format of a save. -

- -#### Save operation details object: - -A save operation details object will have the following properties: - -* **`type`:** (*string*) A string representing how the save operation came about—i.e., what caused it. Possible values are: `'autosave'`, `'disk'`, `'serialize'`, `'slot'`. - -#### Examples: - -##### Using only the save object parameter - -``` -Config.saves.onSave = function (save) { - /* code to post-process the save object */ -}; -``` - -##### Using both the save object and operation details parameters - -``` -Config.saves.onSave = function (save, details) { - switch (details.type) { - case 'autosave': - /* code to post-process the save object from autosaves */ - break; - case 'disk': - /* code to post-process the save object from disk saves */ - break; - case 'serialize': - /* code to post-process the save object from serialize saves */ - break; - default: /* slot */ - /* code to post-process the save object from slot saves */ - break; - } -}; -``` - - - ### `Config.saves.slots` *integer* (default: `8`) {#config-api-property-saves-slots} Sets the maximum number of available save slots. @@ -636,6 +561,33 @@ Config.saves.version = 3; Config.saves.version = "v3"; ``` + + +### `Config.saves.onLoad` ↔ *function* (default: *none*) {#config-api-property-saves-onload} + +

Deprecated: +This setting has been deprecated and should no longer be used. See the Save.onLoad.add() method for its replacement. +

+ +#### History: + +* `v2.0.0`: Introduced. +* `v2.36.0`: Deprecated in favor of the [`Save` Events API](#save-api-events). + + + +### `Config.saves.onSave` ↔ *function* (default: *none*) {#config-api-property-saves-onsave} + +

Deprecated: +This setting has been deprecated and should no longer be used. See the Save.onSave.add() method for its replacement. +

+ +#### History: + +* `v2.0.0`: Introduced. +* `v2.33.0`: Added save operation details object parameter to the callback function. +* `v2.36.0`: Deprecated in favor of the [`Save` Events API](#save-api-events). + -### `Macro.tags.get(name)` → *string array* {#macro-api-method-tags-get} +### `Macro.tags.get(name)` → *Array<string>* {#macro-api-method-tags-get} Return the named macro tag's parents array (includes the names of all macros who have registered the tag as a child), or `null` on failure. diff --git a/docs/api/api-macrocontext.md b/docs/api/api-macrocontext.md index 0fbffee..16663c8 100644 --- a/docs/api/api-macrocontext.md +++ b/docs/api/api-macrocontext.md @@ -11,9 +11,9 @@ Macro handlers are called with no arguments, but with their `this` set to a macr -### `.args` → *array* {#macrocontext-api-prototype-property-args} +### `.args` → *Array<any>* {#macrocontext-api-prototype-property-args} -The argument string parsed into an array of discrete arguments. +An array of discrete arguments parsed from the argument string. #### History: @@ -112,12 +112,12 @@ The parser instance that generated the macro call. -### `.payload` → *null* | *array* {#macrocontext-api-prototype-property-payload} +### `.payload` → *null* | *Array<object>* {#macrocontext-api-prototype-property-payload} The text of a container macro parsed into discrete payload objects by tag. Payload objects have the following properties: * **`name`:** (*string*) Name of the current tag. -* **`args`:** (*array*) The current tag's argument string parsed into an array of discrete arguments. Equivalent in function to [`.args`](#macrocontext-api-prototype-property-args). +* **`args`:** (*Array<any>*) The current tag's argument string parsed into an array of discrete arguments. Equivalent in function to [`.args`](#macrocontext-api-prototype-property-args). * **`args.full`:** (*string*) The current tag's argument string after converting all TwineScript syntax elements into their native JavaScript counterparts. Equivalent in function to [`.args.full`](#macrocontext-api-prototype-property-args-full). * **`args.raw`:** (*string*) The current tag's unprocessed argument string. Equivalent in function to [`.args.raw`](#macrocontext-api-prototype-property-args-raw). * **`contents`:** (*string*) The current tag's contents—i.e., the text between the current tag and the next. @@ -180,7 +180,7 @@ this.contextSelect(includeAncestor); → Returns the first <> macro an -### `.contextSelectAll(filter)` → *object array* {#macrocontext-api-prototype-method-contextselectall} +### `.contextSelectAll(filter)` → *Array<object>* {#macrocontext-api-prototype-method-contextselectall} Returns a new array containing all of the macro's ancestors that passed the test implemented by the given filter function or an empty array, if no members pass. diff --git a/docs/api/api-passage.md b/docs/api/api-passage.md index 2fad5bd..f892d0b 100644 --- a/docs/api/api-passage.md +++ b/docs/api/api-passage.md @@ -19,7 +19,7 @@ The DOM ID of the passage, created from the slugified passage title. -### `.tags` → *string array* {#passage-api-prototype-getter-tags} +### `.tags` → *Array<string>* {#passage-api-prototype-getter-tags} The tags of the passage. diff --git a/docs/api/api-save.md b/docs/api/api-save.md index d7ef0b7..95c439a 100644 --- a/docs/api/api-save.md +++ b/docs/api/api-save.md @@ -32,9 +32,9 @@ Save objects have some of the following properties: The **`state`** object has the following properties: -* **`history`:** (*array*) The array of moment objects (see below for details). +* **`history`:** (*Array<object>*) The array of moment objects (see below for details). * **`index`:** (*integer*) The index of the active moment. -* **`expired`:** (optional, *array*) The array of expired moment passage titles, exists only if any moments have expired. +* **`expired`:** (optional, *Array<string>*) The array of expired moment passage titles, exists only if any moments have expired. * **`seed`:** (optional, *string*) The seed of SugarCube's seedable PRNG, exists only if enabled. Each **`moment`** object has the following properties: @@ -600,3 +600,222 @@ else { /* Failure. An error was displayed to the player. */ } ``` + + + +## Events {#save-api-events} + + + +### `Save.onLoad.add(handler)` {#save-api-method-onload-add} + +Performs any required processing before the save data is loaded—e.g., upgrading out-of-date save data. The handler is passed one parameter, the save object to be processed. If it encounters an unrecoverable problem during its processing, it may throw an exception containing an error message; the message will be displayed to the player and loading of the save will be terminated. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: + +* **`handler`:** (*function*) The handler function to be executed upon the loading of a save. + +#### Handler parameters: + +* **`save`:** (*object*) The [save object](#save-api-save-objects) to be processed. + +#### Examples: + +```js +Save.onLoad.add(function (save) { + /* code to process the save object before it's loaded */ +}); +``` + + + +### `Save.onLoad.clear()` {#save-api-method-onload-clear} + +Deletes all currently registered on-load handlers. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: *none* + +#### Examples: + +```js +Save.onLoad.clear(); +``` + + + +### `Save.onLoad.delete(handler)` → *boolean* {#save-api-method-onload-delete} + +Deletes the specified on-load handler. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: + +* **`handler`:** (*function*) The handler function to be deleted. + +#### Examples: + +```js +// Given: +// let myOnLoadHandler = function (save) { +// /* code to process the save object before it's loaded */ +// }; +// Save.onLoad.add(myOnLoadHandler); + +Save.onLoad.delete(myOnLoadHandler); +``` + + + +### `Save.onLoad.size` → *integer* {#save-api-getter-onload-size} + +Returns the number of currently registered on-load handlers. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: *none* + +#### Examples: + +```js +console.log('There are %d onLoad handlers registered.', Save.onLoad.size); +``` + + + +### `Save.onSave.add(handler)` {#save-api-method-onsave-add} + +Performs any required processing before the save data is saved. The handlers is passed two parameters, the save object to be processed and save operation details object. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: + +* **`handler`:** (*function*) The handler function to be executed upon the saving of a save. + +#### Handler parameters: + +* **`save`:** (*object*) The [save object](#save-api-save-objects) to be processed. +* **`details`:** (*object*) The save operation details object. + +#### Save operation details object: + +A save operation details object will have the following properties: + +* **`type`:** (*string*) A string representing what caused the save operation. Possible values are: `'autosave'`, `'disk'`, `'serialize'`, `'slot'`. + +#### Examples: + +##### Using only the save object parameter + +```js +Save.onSave.add(function (save) { + /* code to process the save object before it's saved */ +}); +``` + +##### Using both the save object and operation details parameters + +```js +Save.onSave.add(function (save, details) { + switch (details.type) { + case 'autosave': { + /* code to process the save object from autosaves before it's saved */ + break; + } + + case 'disk': { + /* code to process the save object from disk saves before it's saved */ + break; + } + + case 'serialize': { + /* code to process the save object from serialize saves before it's saved */ + break; + } + + default: { /* slot */ + /* code to process the save object from slot saves before it's saved */ + break; + } + } +}); +``` + + + +### `Save.onSave.clear()` {#save-api-method-onsave-clear} + +Deletes all currently registered on-save handlers. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: *none* + +#### Examples: + +```js +Save.onSave.clear(); +``` + + + +### `Save.onSave.delete(handler)` → *boolean* {#save-api-method-onsave-delete} + +Deletes the specified on-save handler. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: + +* **`handler`:** (*function*) The handler function to be deleted. + +#### Examples: + +```js +// Given: +// let myOnSaveHandler = function (save, details) { +// /* code to process the save object before it's saved */ +// }; +// Save.onSave.add(myOnSaveHandler); + +Save.onSave.delete(myOnSaveHandler); +``` + + + +### `Save.onSave.size` → *integer* {#save-api-getter-onsave-size} + +Returns the number of currently registered on-save handlers. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: *none* + +#### Examples: + +```js +console.log('There are %d onSave handlers registered.', Save.onSave.size); +``` diff --git a/docs/api/api-setting.md b/docs/api/api-setting.md index 6ac47d1..872538c 100644 --- a/docs/api/api-setting.md +++ b/docs/api/api-setting.md @@ -120,7 +120,7 @@ Adds the named property to the `settings` object and a list control for it to th A list definition object should have some of the following properties: * **`label`:** (*string*) Label to use for the control. -* **`list`:** (*array*) The array of items. +* **`list`:** (*Array<string>*) The array of items. * **`desc`:** (optional, *string*) Description explaining the control in greater detail. * **`default`:** (optional, *[as **`list`** array]*) The default value for the setting and default state of the control. It should have the same value as one of the members of the **`list`** array. Leaving it undefined means to use the first array member as the default. * **`onInit`:** (optional, *function*) The function to call during initialization. diff --git a/docs/api/api-simpleaudio.md b/docs/api/api-simpleaudio.md index c733627..646d026 100644 --- a/docs/api/api-simpleaudio.md +++ b/docs/api/api-simpleaudio.md @@ -253,7 +253,7 @@ Adds an audio track with the given track ID. #### Parameters: * **`trackId`:** (*string*) The ID of the track, which will be used to reference it. -* **`sources`:** (*any* | *array*) The audio sources for the track, which may be a list of sources or an array. Only one is required, though supplying additional sources in differing formats is recommended, as no single format is supported by all browsers. A source must be either a URL (absolute or relative) to an audio resource, the name of an audio passage, or a data URI. In rare cases where the audio format cannot be automatically detected from the source (URLs are parsed for a file extension, data URIs are parsed for the media type), a format specifier may be prepended to the front of each source to manually specify the format (syntax: `formatId|`, where `formatId` is the audio format—generally, whatever the file extension would normally be; e.g., `mp3`, `mp4`, `ogg`, `weba`, `wav`). +* **`sources`:** (*string*… | *Array<string>*) The audio sources for the track, which may be a list of sources or an array. Only one is required, though supplying additional sources in differing formats is recommended, as no single format is supported by all browsers. A source must be either a URL (absolute or relative) to an audio resource, the name of an audio passage, or a data URI. In rare cases where the audio format cannot be automatically detected from the source (URLs are parsed for a file extension, data URIs are parsed for the media type), a format specifier may be prepended to the front of each source to manually specify the format (syntax: `formatId|`, where `formatId` is the audio format—generally, whatever the file extension would normally be; e.g., `mp3`, `mp4`, `ogg`, `weba`, `wav`). #### Examples: @@ -399,7 +399,7 @@ If you want to play tracks in a sequence, then you want a -### `SimpleAudio.groups.get(groupId)` → *array* | *null* {#simpleaudio-api-method-groups-get} +### `SimpleAudio.groups.get(groupId)` → *Array<string>* | *null* {#simpleaudio-api-method-groups-get} Returns the array of track IDs with the given group ID, or `null` on failure. @@ -524,7 +524,7 @@ If you simply want to apply actions to multiple tracks simultaneously, then you #### Parameters: * **`listId`:** (*string*) The ID of the list, which will be used to reference it. -* **`sources`:** (*string* | *object* | *array*) The track IDs or descriptors of the tracks to make part of the list, which may be specified as a list or an array. +* **`sources`:** (*string* | *object* | *Array<string | object>*) The track IDs or descriptors of the tracks to make part of the list, which may be specified as a list or an array. #### Descriptor objects: @@ -535,7 +535,7 @@ Track descriptor objects come in two forms and should have some of the noted pro * **`own`:** (optional, *boolean*) When `true`, signifies that the playlist should create its own independent copy of the track, rather than simply referencing the existing instance. Owned copies are solely under the control of their playlist and cannot be selected with either the [`SimpleAudio.tracks.get()` method](#simpleaudio-api-method-tracks-get) or the [`SimpleAudio.select()` method](#simpleaudio-api-method-select). * **`volume`:** (optional, *number*) The base volume level of the track within the playlist. If omitted, defaults to the track's current volume. Valid values are floating-point numbers in the range `0` (silent) to `1` (loudest)—e.g., `0` is 0%, `0.5` is 50%, `1` is 100%. * **New track form: `{ sources, [volume] }`** - * **`sources`:** (*string array*) The audio sources for the track. Only one is required, though supplying additional sources in differing formats is recommended, as no single format is supported by all browsers. A source must be either a URL (absolute or relative) to an audio resource, the name of an audio passage, or a data URI. In rare cases where the audio format cannot be automatically detected from the source (URLs are parsed for a file extension, data URIs are parsed for the media type), a format specifier may be prepended to the front of each source to manually specify the format (syntax: `formatId|`, where `formatId` is the audio format—generally, whatever the file extension would normally be; e.g., `mp3`, `mp4`, `ogg`, `weba`, `wav`). + * **`sources`:** (*Array<string>*) The audio sources for the track. Only one is required, though supplying additional sources in differing formats is recommended, as no single format is supported by all browsers. A source must be either a URL (absolute or relative) to an audio resource, the name of an audio passage, or a data URI. In rare cases where the audio format cannot be automatically detected from the source (URLs are parsed for a file extension, data URIs are parsed for the media type), a format specifier may be prepended to the front of each source to manually specify the format (syntax: `formatId|`, where `formatId` is the audio format—generally, whatever the file extension would normally be; e.g., `mp3`, `mp4`, `ogg`, `weba`, `wav`). * **`volume`:** (optional, *number*) The base volume level of the track within the playlist. If omitted, defaults to `1` (loudest). Valid values are floating-point numbers in the range `0` (silent) to `1` (loudest)—e.g., `0` is 0%, `0.5` is 50%, `1` is 100%. #### Examples: diff --git a/docs/api/api-state.md b/docs/api/api-state.md index 6cd40fc..4dbdbef 100644 --- a/docs/api/api-state.md +++ b/docs/api/api-state.md @@ -387,6 +387,41 @@ State.metadata.delete('achievements'); +### `State.metadata.entries()` → *Array<Array<string, any>>* {#state-api-method-metadata-entries} + +Returns an array of the story metadata store's key/value pairs as `[key, value]` arrays. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: *none* + +#### Examples: + +``` +// Get the metadata store's key/value pairs. +var metadata = State.metadata.entries(); + +// Iterate over the pairs with a `for` loop. +for (var i = 0; i < metadata.length; ++i) { + var key = metadata[i][0]; + var value = metadata[i][1]; + + /* do something */ +} + +// Iterate over the pairs with `.forEach()`. +metadata.forEach(function (pair) { + var key = pair[0]; + var value = pair[1]; + + /* do something */ +}); +``` + + + ### `State.metadata.get(key)` → *any* {#state-api-method-metadata-get} Returns the value associated with the specified key from the story metadata store. @@ -431,6 +466,37 @@ if (State.metadata.has('achievements')) { +### `State.metadata.keys()` → *Array<string>* {#state-api-method-metadata-keys} + +Returns an array of the story metadata store's keys. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: *none* + +#### Examples: + +``` +// Get the metadata store's keys. +var metadataKeys = State.metadata.keys(); + +// Iterate over the keys with a `for` loop. +for (var i = 0; i < metadataKeys.length; ++i) { + var key = metadataKeys[i]; + + /* do something */ +} + +// Iterate over the keys with `.forEach()`. +metadataKeys.forEach(function (key) { + /* do something */ +}); +``` + + + ### `State.metadata.set(key, value)` {#state-api-method-metadata-set} Sets the specified key and value within the story metadata store, which causes them to persist over story and browser restarts—n.b. private browsing modes do interfere with this. To update the value associated with a key, simply set it again. diff --git a/docs/api/api-template.md b/docs/api/api-template.md index 8335cbe..a79562c 100644 --- a/docs/api/api-template.md +++ b/docs/api/api-template.md @@ -33,8 +33,8 @@ Add new template(s). #### Parameters: -* **`name`:** (*string* | *string array*) Name, or array of names, of the template(s) to add. **NOTE:** Names must consist of characters from the basic Latin alphabet and start with a letter, which may be optionally followed by any number of letters, numbers, the underscore, or the hyphen. -* **`definition`:** (*function* | *string* | *array*) Definition of the template(s), which may be a: function, string, or an array of either. **NOTE:** Each time array definitions are referenced, one of their member templates is randomly selected to be the output source. +* **`name`:** (*string* | *Array<string>*) Name, or array of names, of the template(s) to add. **NOTE:** Names must consist of characters from the basic Latin alphabet and start with a letter, which may be optionally followed by any number of letters, numbers, the underscore, or the hyphen. +* **`definition`:** (*function* | *string* | *Array<function | string>*) Definition of the template(s), which may be a: function, string, or an array of either. **NOTE:** Each time array definitions are referenced, one of their member templates is randomly selected to be the output source. #### Function templates: @@ -94,7 +94,7 @@ Remove existing template(s). #### Parameters: -* **`name`:** (*string* | *string array*) Name, or array of names, of the template(s) to remove. +* **`name`:** (*string* | *Array<string>*) Name, or array of names, of the template(s) to remove. #### Examples: @@ -108,7 +108,7 @@ Template.delete(['yolo', 'nolf']); -### `Template.get(name)` → *function* | *string* | *array* {#template-api-method-get} +### `Template.get(name)` → *function* | *string* | *Array<function | string>* {#template-api-method-get} Return the named template definition, or `null` on failure. diff --git a/docs/build-docs.js b/docs/build-docs.js new file mode 100644 index 0000000..2099b07 --- /dev/null +++ b/docs/build-docs.js @@ -0,0 +1,256 @@ +#!/usr/bin/env node +/*********************************************************************************************************************** + + build-docs.js (v1.1.0, 2021-12-19) + A Node.js-hosted build script for SugarCube's documentation. + + Copyright © 2020–2021 Thomas Michael Edwards . All rights reserved. + Use of this source code is governed by a BSD 2-clause "Simplified" License, which may be found in the LICENSE file. + +***********************************************************************************************************************/ +/* eslint-env node, es6 */ +'use strict'; + +/******************************************************************************* + Configuration +*******************************************************************************/ +const CONFIG = { + // WARNING: The ordering within the following arrays is significant. + md : { + files : [ + // Table of Contents + 'table-of-contents.md', + + // Introduction + 'introduction.md', + + // Core + 'core/markup.md', + 'core/twinescript.md', + 'core/macros.md', + 'core/functions.md', + 'core/methods.md', + 'core/special-names.md', + 'core/css.md', + 'core/html.md', + 'core/events.md', + + // APIs + 'api/api-config.md', + 'api/api-dialog.md', + 'api/api-engine.md', + 'api/api-fullscreen.md', + 'api/api-loadscreen.md', + 'api/api-macro.md', + 'api/api-macrocontext.md', + 'api/api-passage.md', + 'api/api-save.md', + 'api/api-setting.md', + 'api/api-simpleaudio.md', + 'api/api-simpleaudio-audiotrack.md', + 'api/api-simpleaudio-audiorunner.md', + 'api/api-simpleaudio-audiolist.md', + 'api/api-state.md', + 'api/api-story.md', + 'api/api-template.md', + 'api/api-ui.md', + 'api/api-uibar.md', + + // Guides + 'guides/guide-state-sessions-and-saving.md', + 'guides/guide-tips.md', + 'guides/guide-media-passages.md', + 'guides/guide-harlowe-to-sugarcube.md', + 'guides/guide-test-mode.md', + 'guides/guide-typescript.md', + 'guides/guide-installation.md', + 'guides/guide-code-updates.md', + 'guides/guide-localization.md' + ] + }, + js : { + intro : { + files : [ + 'templates/js/intro-scdocs.js', + 'templates/js/intro-enhancement.js' + ], + intro : 'templates/js/intro.js', + outro : 'templates/js/outro.js' + }, + nav : { + files : [ + 'templates/js/nav-enhancement.js' + ], + intro : 'templates/js/intro.js', + outro : 'templates/js/outro.js' + } + }, + css : { + files : [ + 'templates/css/core.css' + ] + }, + html : { + intro : 'templates/html/intro.html', + outro : 'templates/html/outro.html' + }, + build : { + dest : 'build/index.html' + } +}; + + +/******************************************************************************* + Main Script +*******************************************************************************/ +/* + NOTICE! + + Where string replacements are done, we use the replacement function style to + disable all special replacement patterns, since some of them may exist within + the replacement strings—e.g., '$&' within the HTML or JavaScript sources. +*/ + +const process = require('process'); +process.env.BROWSERSLIST_CONFIG = '../browserslist'; + +const { + log, + die, + makePath, + readFileContents, + writeFileContents, + concatFiles +} = require('../scripts/build-utils'); +const _path = require('path'); +const _opt = require('node-getopt').create([ + ['h', 'help', 'Print this help, then exit.'], + ['u', 'unminified', 'Suppress minification stages.'] +]) + .bindHelp() // bind option 'help' to default action + .parseSystem(); // parse command line + +// Build the documentation. +(() => { + // Set build constants. + const BUILD_VERSION = require('../package.json').version; + const BUILD_DATETIME = new Date().toISOString().replace(/\.\d+Z$/, 'Z'); + const BUILD_DATE = new Date().toISOString().slice(0, 10); + + // Compile the JavaScript. + log('compiling JavaScript...'); + const jsIntro = compileJavaScript(CONFIG.js.intro); + const jsNav = compileJavaScript(CONFIG.js.nav); + + // Compile the CSS. + log('compiling CSS...'); + const css = compileStyles(CONFIG.css); + + // Compile the Markdown. + log('compiling Markdown...'); + const markdown = compileMarkdown(CONFIG.md); + + // Assemble the basic document. + log('assembling document...'); + let output = readFileContents(CONFIG.html.intro) + + markdown + + readFileContents(CONFIG.html.outro); + + // Add permalinks to ID-bearing headings. + output = output.replace( + /<([Hh]\d)>(.+?)\{#([^}]+)\}<\/\1>/g, + (_, heading, text, id) => `<${heading} id="${id}">\u00A0${text}` + ); + + // Build the final document. + const outfile = _path.normalize(CONFIG.build.dest); + log(`building: "${outfile}"`); + + // Process the source replacement tokens. (First!) + output = output.replace(/\{\{\.SCRIPT_CORE\}\}/, () => jsIntro); + output = output.replace(/\{\{\.SCRIPT_NAV\}\}/, () => jsNav); + output = output.replace(/\{\{\.STYLE_CORE\}\}/, () => css); + + // Process the build replacement tokens. + output = output.replace(/\{\{\.VERSION\}\}/g, () => BUILD_VERSION); + output = output.replace(/\{\{\.ISO_DATE\}\}/g, () => BUILD_DATETIME); + output = output.replace(/\{\{\.DATE\}\}/g, () => BUILD_DATE); + + // Write the outfile. + makePath(_path.dirname(outfile)); + writeFileContents(outfile, output); +})(); + + +/******************************************************************************* + Utility Functions +*******************************************************************************/ +function compileMarkdown(sourceConfig) { + /* eslint-disable prefer-template */ + const { execFileSync } = require('child_process'); + return concatFiles(sourceConfig.files, (contents /* , filename */) => { + try { + // TODO: Replace `cmark-gfm` with a JavaScript solution. + return execFileSync('cmark-gfm', ['-t', 'html'], { + input : contents + }); + } + catch (ex) { + die(`markdown error: ${ex.message}`, ex); + } + }); + /* eslint-enable prefer-template */ +} + +function compileJavaScript(sourceConfig) { + /* eslint-disable camelcase, prefer-template */ + const jsSource = readFileContents(sourceConfig.intro) + + concatFiles(sourceConfig.files) + + readFileContents(sourceConfig.outro); + + if (_opt.options.unminified) { + return jsSource; + } + + const uglifyjs = require('uglify-js'); + const minified = uglifyjs.minify(jsSource, { + compress : { + keep_infinity : true + }, + mangle : true + }); + + if (minified.error) { + const { message, line, col, pos } = minified.error; + die(`JavaScript minification error: ${message}\n[@: ${line}/${col}/${pos}]`); + } + + return minified.code; + /* eslint-enable camelcase, prefer-template */ +} + +function compileStyles(sourceConfig) { + /* eslint-disable prefer-template */ + const autoprefixer = require('autoprefixer'); + const postcss = require('postcss'); + const CleanCss = require('clean-css'); + return concatFiles(sourceConfig.files, (contents, filename) => { + const processed = postcss([autoprefixer]).process(contents, { from : filename }); + processed.warnings().forEach(mesg => console.warn(mesg.text)); + + let css = processed.css; + + if (!_opt.options.unminified) { + css = new CleanCss({ + level : 1, // [clean-css v4] `1` is the default, but let's be specific + compatibility : 'ie9' // [clean-css v4] 'ie10' is the default, so restore IE9 support + }) + .minify(css) + .styles; + } + + return ''; + }); + /* eslint-enable prefer-template */ +} diff --git a/docs/core/functions.md b/docs/core/functions.md index 82708c5..67b37bb 100644 --- a/docs/core/functions.md +++ b/docs/core/functions.md @@ -103,7 +103,7 @@ Returns whether the passage with the given title occurred within the story histo #### Parameters: -* **`passages`:** (*string* | *string array*) The title(s) of the passage(s) to search for. May be a list or an array of passages. +* **`passages`:** (*string* | *Array<string>*) The title(s) of the passage(s) to search for. May be a list or an array of passages. #### Examples: @@ -126,7 +126,7 @@ Returns the number of turns that have passed since the last instance of the pass #### Parameters: -* **`passages`:** (*string* | *string array*) The title(s) of the passage(s) to search for. May be a list or an array of passages. +* **`passages`:** (*string* | *Array<string>*) The title(s) of the passage(s) to search for. May be a list or an array of passages. #### Examples: @@ -158,7 +158,7 @@ Your project's JavaScript section (Twine 2: the Story JavaScript; Twine&nbs #### Parameters: -* **`urls`:** (*string* | *string array*) The URLs of the external scripts to import. Loose URLs are imported concurrently, arrays of URLs are imported sequentially. +* **`urls`:** (*string* | *Array<string>*) The URLs of the external scripts to import. Loose URLs are imported concurrently, arrays of URLs are imported sequentially. #### Examples: @@ -246,7 +246,7 @@ Your project's JavaScript section (Twine 2: the Story JavaScript; Twine&nbs #### Parameters: -* **`urls`:** (*string* | *string array*) The URLs of the external stylesheets to import. Loose URLs are imported concurrently, arrays of URLs are imported sequentially. +* **`urls`:** (*string* | *Array<string>*) The URLs of the external stylesheets to import. Loose URLs are imported concurrently, arrays of URLs are imported sequentially. #### Examples: @@ -474,7 +474,7 @@ Renders the selected passage into the target element, replacing any existing con #### Parameters: * **`idOrElement`:** (*string* | *`HTMLElement` object*) The ID of the element or the element itself. -* **`passages`:** (*string* | *string array*) The name(s) of the passage(s) to search for. May be a single passage or an array of passages. If an array of passage names is specified, the first passage to be found is used. +* **`passages`:** (*string* | *Array<string>*) The name(s) of the passage(s) to search for. May be a single passage or an array of passages. If an array of passage names is specified, the first passage to be found is used. * **`defaultText`:** (optional, *string*) The default text to use if no passages are found. #### Examples: @@ -493,7 +493,7 @@ setPageElement(myElement, "MyPassage"); -### `tags([passages…])` → *string array* {#functions-function-tags} +### `tags([passages…])` → *Array<string>* {#functions-function-tags} Returns a new array consisting of all of the tags of the given passages. @@ -503,7 +503,7 @@ Returns a new array consisting of all of the tags of the given passages. #### Parameters: -* **`passages`:** (optional, *string* | *string array*) The passages from which to collect tags. May be a list or an array of passages. If omitted, will default to the active (present) passage—included passages do not count for this purpose; e.g., passages pulled in via `<>`, `PassageHeader`, etc. +* **`passages`:** (optional, *string* | *Array<string>*) The passages from which to collect tags. May be a list or an array of passages. If omitted, will default to the active (present) passage—included passages do not count for this purpose; e.g., passages pulled in via `<>`, `PassageHeader`, etc. #### Examples: @@ -613,7 +613,7 @@ Returns the number of times that the passage with the given title occurred withi #### Parameters: -* **`passages`:** (optional, *string* | *string array*) The title(s) of the passage(s) to search for. May be a list or an array of passages. If omitted, will default to the current passage. +* **`passages`:** (optional, *string* | *Array<string>*) The title(s) of the passage(s) to search for. May be a list or an array of passages. If omitted, will default to the current passage. #### Examples: @@ -636,7 +636,7 @@ Returns the number of passages within the story history that are tagged with all #### Parameters: -* **`tags`:** (*string* | *string array*) The tags to search for. May be a list or an array of tags. +* **`tags`:** (*string* | *Array<string>*) The tags to search for. May be a list or an array of tags. #### Examples: diff --git a/docs/core/macros.md b/docs/core/macros.md index f7db219..fd17c9c 100644 --- a/docs/core/macros.md +++ b/docs/core/macros.md @@ -987,7 +987,7 @@ Interactive macros are both asynchronous and require interaction from the player ### `<>`
`<>`
`<>` {#macros-macro-button} -Creates a button that silently executes its contents when clicked, optionally forwarding the player to another passage. May be called either with the link text and passage name as separate arguments, with a link markup, or with an image markup. +Creates a button that silently executes its contents when clicked, optionally forwarding the player to another passage. May be called with either the link text and passage name as separate arguments, a link markup, or an image markup.

See: Interactive macro warning. @@ -1093,7 +1093,7 @@ What pies do you enjoy? -### `<>`
`[<
`<
>` {#macros-macro-cycle} +### `<>`
`[<
`<
>` {#macros-macro-cycle} Creates a cycling link, used to modify the value of the variable with the given name. The cycling options are populated via `<

See: Interactive macro warning. @@ -1331,6 +1345,7 @@ Creates a listbox, used to modify the value of the variable with the given name. * `v2.28.0`: Added `<>` child tag. * `v2.28.1`: Fixed name of `<>` child tag, which was erroneously added as `<>` in `v2.28.0`. * `v2.29.0`: Made the `<

Warning: Widgets should always be defined within a widget-tagged passage—any widgets that are not may be lost on page reload—and you may use as few or as many such passages as you desire. Do not add a widget tag to any of the specially named passages and attempt to define your widgets there.

Warning: -The $args array-like object should be treated as though it were immutable—i.e., unable to be modified—because in the future it will be made thus, so any attempt to modify it will cause an error. +The array-like object stored in the _args variable should be treated as though it were immutable—i.e., unable to be modified—because in the future it will be made thus, so any attempt to modify it will cause an error.

#### History: * `v2.0.0`: Introduced. +* `v2.36.0`: Added the `container` keyword, `_args` variable, and `_contents` variable. Deprecated the `$args` variable in favor of `_args`. #### Arguments: * **`widgetName`:** The name of the created widget, which should not contain whitespace or angle brackets (`<`, `>`). If the name of an existing widget is chosen, the new widget will overwrite the older version. **NOTE:** The names of existing macros are invalid widget names and any attempts to use such a name will cause an error. +* **`container`:** (optional) Keyword, used to signify that the widget should be created as a container widget—i.e., non-void, requiring a closing tag; e.g., `<>…<>`. -#### `$args` array-like object: +#### Special variables, `_args` & `_contents`: -The `$args` variable is used internally to store arguments passed to the widget—as zero-based indices; i.e., `$args[0]` is the first parsed argument, `$args[1]` is the second, etc—and the full argument string in raw and parsed forms—accessed via the `$args.raw` and `$args.full` properties. +The `_args` special variable is used internally to store arguments passed to the widget—as zero-based indices; i.e., `_args[0]` is the first parsed argument, `_args[1]` is the second, etc—and the full argument string in raw and parsed forms—accessed via the `_args.raw` and `_args.full` properties. -When a widget is called, any existing `$args` variable is stored for the duration of the call and restored after. This means that non-widget use of an `$args` variable is completely safe, though this does have the effect that an `$args` variable external to a widget is inaccessible to it unless passed in as an argument. +The `_contents` special variable is used internally, by container widgets, to store the contents they enclose. -Unless localized by use of the [`<>` macro](#macros-macro-capture), any story or temporary variables used within widgets are part of a story's normal variable store, so care *must be* taken not to accidentally either overwrite or pick up an existing value. +When a widget is called, any existing `_args` variable, and for container widgets `_contents`, is stored for the duration of the call and restored after. This means that non-widget uses of these special variable are completely safe, though this does have the effect that uses external to widgets are inaccessible within them unless passed in as arguments. + +

Warning: +Unless localized by use of the <<capture>> macro, any story or other temporary variables used within widgets are part of a story's normal variable store, so care must be taken not to accidentally either overwrite or pick up an existing value. +

#### Examples: +

Note: +No line-break control mechanisms are used in the following examples for readability. In practice, you'll probably want to use either line continuations or one of the no-break methods: Config.passages.nobr setting, nobr special tag, <<nobr>> macro. +

+ +##### Basic usage (non-container) + ``` → Creating a gender pronoun widget -<><>he<>she<>it<><> +<> + <> + he + <> + she + <> + it + <> +<> + → Using it "Are you sure that <> can be trusted?" +``` +``` → Creating a silly print widget -<><><><>Mum's the word!<><> +<> + <> + <> + <> + Mum's the word! + <> +<> + → Using it <> → Outputs: Mum's the word! <> → Outputs: Hi! ``` + +##### Basic usage (container) + +``` +→ Creating a simple dialog box widget +<> +
+ +

_contents

+
+<
> + +→ Using it +<>Tweego is a pathway to many abilities some consider to be… unnatural.<> +``` diff --git a/docs/core/methods.md b/docs/core/methods.md index b945d2c..d0de5e7 100644 --- a/docs/core/methods.md +++ b/docs/core/methods.md @@ -20,7 +20,7 @@ Additionally. SugarCube includes polyfills for virtually all JavaScript (ECMASc -### `.concat(members…)` → *array* {#methods-array-prototype-method-concat} +### `.concat(members…)` → *Array<any>* {#methods-array-prototype-method-concat} Concatenates one or more members to the end of the base array and returns the result as a new array. Does not modify the original. @@ -28,7 +28,7 @@ Concatenates one or more members to the end of the base array and returns the re #### Parameters: -* **`members`:** (*any*) The members to concatenate. Members that are arrays will be merged—i.e., their members will be concatenated, rather than the array itself. +* **`members`:** (*any*…) The members to concatenate. Members that are arrays will be merged—i.e., their members will be concatenated, rather than the array itself. #### Examples: @@ -43,7 +43,7 @@ $fruits1.concat($fruits2, "Pears") → Returns ["Apples", "Oranges", "Pears", -### `.concatUnique(members…)` → *array* {#methods-array-prototype-method-concatunique} +### `.concatUnique(members…)` → *Array<any>* {#methods-array-prototype-method-concatunique} Concatenates one or more unique members to the end of the base array and returns the result as a new array. Does not modify the original. @@ -53,7 +53,7 @@ Concatenates one or more unique members to the end of the base array and returns #### Parameters: -* **`members`:** (*any*) The members to concatenate. Members that are arrays will be merged—i.e., their members will be concatenated, rather than the array itself. +* **`members`:** (*any*…) The members to concatenate. Members that are arrays will be merged—i.e., their members will be concatenated, rather than the array itself. #### Examples: @@ -91,7 +91,49 @@ $fruits.count("Oranges", 2) → Returns 1 -### `.delete(needles…)` → *array* {#methods-array-prototype-method-delete} +### `.countWith(predicate [, thisArg])` → *integer* {#methods-array-prototype-method-countwith} + +Returns the number of times that members within the array pass the test implemented by the given predicate function. + +#### History: + +* `v2.36.0`: Introduced. + +#### Parameters: + +* **`predicate`:** (*function*) The function used to test each member. It is called with three arguments: + * **`value`:** (*any*) The member being processed. + * **`index`:** (optional, *integer*) The index of member being processed. + * **`array`:** (optional, *array*) The array being processed. +* **`thisArg`:** (optional, *any*) The value to use as `this` when executing `predicate`. + +#### Examples: + +``` +// Given: $fruits = ["Apples", "Oranges", "Plums", "Oranges"] +$fruits.countWith(function (fruit) { return fruit === "Oranges"; }) → Returns 2 +``` + +``` +// Given: $numbers = [1, 2.3, 4, 76, 3.1] +$numbers.countWith(Number.isInteger) → Returns 3 +``` + +``` +// Given: $items = [ +// { name : 'Healing potion', kind : 'potion' }, +// { name : 'Longsword', kind : 'weapon' }, +// { name : 'Mana potion', kind : 'potion' }, +// { name : 'Dead rat', kind : 'junk' }, +// { name : 'Endurance potion', kind : 'potion' }, +// { name : 'Shortbow', kind : 'weapon' } +// ] +$items.countWith(function (item) { return item.kind === 'junk'; }) → Returns 1 +``` + + + +### `.delete(needles…)` → *Array<any>* {#methods-array-prototype-method-delete} Removes all instances of the given members from the array and returns a new array containing the removed members. @@ -101,7 +143,7 @@ Removes all instances of the given members from the array and returns a new arra #### Parameters: -* **`needles`:** (*any* | *array*) The members to remove. May be a list of members or an array. +* **`needles`:** (*any*… | *Array<any>*) The members to remove. May be a list of members or an array. #### Examples: @@ -113,7 +155,7 @@ $fruits.delete("Apples", "Plums") → Returns ["Apples", "Plums"]; $fruits ["Or -### `.deleteAt(indices…)` → *array* {#methods-array-prototype-method-deleteat} +### `.deleteAt(indices…)` → *Array<any>* {#methods-array-prototype-method-deleteat} Removes all of the members at the given indices from the array and returns a new array containing the removed members. @@ -123,7 +165,7 @@ Removes all of the members at the given indices from the array and returns a new #### Parameters: -* **`indices`:** (*integer* | *array*) The indices of the members to remove. May be a list of indices or an array. +* **`indices`:** (*integer*… | *Array<integer>*) The indices of the members to remove. May be a list or array of indices. #### Examples: @@ -136,9 +178,9 @@ $fruits.deleteAt(0, 2) → Returns ["Apples", "Plums"]; $fruits ["Oranges", "Or -### `.deleteWith(predicate [, thisArg])` → *array* {#methods-array-prototype-method-deletewith} +### `.deleteWith(predicate [, thisArg])` → *Array<any>* {#methods-array-prototype-method-deletewith} -Removes all of the members that pass the test implemented by the given predicate function from the array and returns a new array containing the removed members. +Removes all of the members from the array that pass the test implemented by the given predicate function and returns a new array containing the removed members. #### History: @@ -201,7 +243,7 @@ $pies.first() → Returns "Blueberry" -### `.flat(depth)` → *array* {#methods-array-prototype-method-flat} +### `.flat(depth)` → *Array<any>* {#methods-array-prototype-method-flat} Returns a new array consisting of the source array with all sub-array elements concatenated into it recursively up to the given depth. Does not modify the original. @@ -223,7 +265,7 @@ $npa.flat(2) → Returns ["Alfa", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot -### `.flatMap(callback [, thisArg])` → *array* {#methods-array-prototype-method-flatmap} +### `.flatMap(callback [, thisArg])` → *Array<any>* {#methods-array-prototype-method-flatmap} Returns a new array consisting of the result of calling the given mapping function on every element in the source array and then concatenating all sub-array elements into it recursively up to a depth of `1`. Does not modify the original. @@ -285,7 +327,7 @@ Returns whether all of the given members were found within the array. #### Parameters: -* **`needles`:** (*any* | *array*) The members to find. May be a list of members or an array. +* **`needles`:** (*any*… | *Array<any>*) The members to find. May be a list of members or an array. #### Examples: @@ -309,7 +351,7 @@ Returns whether any of the given members were found within the array. #### Parameters: -* **`needles`:** (*any* | *array*) The members to find. May be a list of members or an array. +* **`needles`:** (*any*… | *Array<any>*) The members to find. May be a list of members or an array. #### Examples: @@ -361,7 +403,7 @@ $pies.pluck() → Removes and returns a random pie from the array -### `.pluckMany(want)` → *array* {#methods-array-prototype-method-pluckmany} +### `.pluckMany(want)` → *Array<any>* {#methods-array-prototype-method-pluckmany} Randomly removes the given number of members from the base array and returns the removed members as a new array. @@ -407,7 +449,7 @@ Appends one or more members to the end of the base array and returns its new len #### Parameters: -* **`members`:** (*any*) The members to append. +* **`members`:** (*any*…) The members to append. #### Examples: @@ -431,7 +473,7 @@ Appends one or more unique members to the end of the base array and returns its #### Parameters: -* **`members`:** (*any*) The members to append. +* **`members`:** (*any*…) The members to append. #### Examples: @@ -464,7 +506,7 @@ $pies.random() → Returns a random pie from the array -### `.randomMany(want)` → *array* {#methods-array-prototype-method-randommany} +### `.randomMany(want)` → *Array<any>* {#methods-array-prototype-method-randommany} Randomly selects the given number of unique members from the base array and returns the selected members as a new array. Does not modify the original. @@ -502,7 +544,7 @@ $fruits.shift() → Returns "Apples"; $fruits ["Oranges", "Pears"] -### `.shuffle()` → *array* {#methods-array-prototype-method-shuffle} +### `.shuffle()` → *Array<any>* {#methods-array-prototype-method-shuffle} Randomly shuffles the array. @@ -529,7 +571,7 @@ Prepends one or more members to the beginning of the base array and returns its #### Parameters: -* **`members`:** (*any*) The members to append. +* **`members`:** (*any*…) The members to append. #### Examples: @@ -553,7 +595,7 @@ Prepends one or more unique members to the beginning of the base array and retur #### Parameters: -* **`members`:** (*any*) The members to append. +* **`members`:** (*any*…) The members to append. #### Examples: @@ -606,7 +648,7 @@ This method has been deprecated and should no longer be used. See the `.flatten()` → *array* {#methods-array-prototype-method-flatten} +### `.flatten()` → *Array<any>* {#methods-array-prototype-method-flatten}

Deprecated: This method has been deprecated and should no longer be used. See the <Array>.flat() method for its replacement. The exactly equivalent call is: <Array>.flat(Infinity). @@ -806,7 +848,7 @@ Wikifies the given content source(s) and discards the result. If there were err #### Parameters: -* **`sources`:** (*string*) The list of content sources. +* **`sources`:** (*string*…) The list of content sources. #### Examples: @@ -826,7 +868,7 @@ Wikifies the given content source(s) and appends the result to the target elemen #### Parameters: -* **`sources`:** (*string*) The list of content sources. +* **`sources`:** (*string*…) The list of content sources. #### Examples: @@ -1010,7 +1052,7 @@ Returns a formatted string, after replacing each format item in the given format #### Parameters: * **`format`:** (*string*) The format string, which consists of normal text and format items. -* **`arguments`:** (*any* | *array*) Either a list of arguments, which correspond by-index to the format items within the format string, or an array, whose members correspond by-index. +* **`arguments`:** (*any*… | *Array<any>*) Either a list of arguments, which correspond by-index to the format items within the format string, or an array, whose members correspond by-index. #### Format items: diff --git a/docs/core/special-names.md b/docs/core/special-names.md index 5b33a00..855e7d0 100644 --- a/docs/core/special-names.md +++ b/docs/core/special-names.md @@ -20,7 +20,9 @@ Passage, tag, and variable names that have special meaning to SugarCube. ### `PassageDone` {#special-passage-passagedone} -Used for post-passage-display tasks, like redoing dynamic changes (happens after the rendering and display of each passage). Roughly equivalent to the [`:passagedisplay` event](#events-navigation-event-passagedisplay). +Used for post-passage-display tasks, like redoing dynamic changes (happens after the rendering and display of each passage). Generates no output. + +Roughly equivalent to the [`:passagedisplay` event](#events-navigation-event-passagedisplay). #### History: @@ -30,7 +32,9 @@ Used for post-passage-display tasks, like redoing dynamic changes (happens after ### `PassageFooter` {#special-passage-passagefooter} -Appended to each rendered passage. Roughly equivalent to the [`:passagerender` event](#events-navigation-event-passagerender). +Appended to each rendered passage. + +Roughly equivalent to the [`:passagerender` event](#events-navigation-event-passagerender). #### History: @@ -40,7 +44,9 @@ Appended to each rendered passage. Roughly equivalent to the [`:passagerender` ### `PassageHeader` {#special-passage-passageheader} -Prepended to each rendered passage. Roughly equivalent to the [`:passagestart` event](#events-navigation-event-passagestart). +Prepended to each rendered passage. + +Roughly equivalent to the [`:passagestart` event](#events-navigation-event-passagestart). #### History: @@ -50,7 +56,9 @@ Prepended to each rendered passage. Roughly equivalent to the [`:passagestart` ### `PassageReady` {#special-passage-passageready} -Used for pre-passage-display tasks, like redoing dynamic changes (happens before the rendering of each passage). Roughly equivalent to the [`:passagestart` event](#events-navigation-event-passagestart). +Used for pre-passage-display tasks, like redoing dynamic changes (happens before the rendering of each passage). Generates no output. + +Roughly equivalent to the [`:passagestart` event](#events-navigation-event-passagestart). #### History: @@ -112,7 +120,7 @@ Sets the story's display title in the browser's titlebar and the UI bar (element ### `StoryInit` {#special-passage-storyinit} -Used for pre-story-start initialization tasks, like variable initialization (happens at the beginning of story initialization). +Used for pre-story-start initialization tasks, like variable initialization (happens at the beginning of story initialization). Generates no output. #### History: @@ -122,16 +130,19 @@ Used for pre-story-start initialization tasks, like variable initialization (hap ### `StoryInterface` {#special-passage-storyinterface} -Used to replace SugarCube's default UI. Its contents are treated as raw HTML markup—i.e., *none* of SugarCube's special HTML processing is performed. It must contain, at least, an element with the ID `passages`, which will be the main passage display area. Elements, aside from the `#passages` element, may include a `data-passage` content attribute, which denotes that the element should be updated via the specified passage—the passage will be processed as normal, meaning that markup and macros will work as expected. +Used to replace SugarCube's default UI. Its contents are treated as raw HTML markup—i.e., *none* of SugarCube's special HTML processing is performed. It must contain, at least, an element with the ID `passages` that will be the main passage display area. + +Additional elements, aside from the `#passages` element, may include either the `data-init-passage` or `data-passage` content attribute, whose value is the name of the passage used to populate the element—the passage will be processed as normal, meaning that markup and macros will work as expected. The `data-init-passage` attribute causes the element to be updated once at initialization, while the `data-passage` attribute causes the element to be updated upon each passage navigation.

Warning: -Elements that include a data-passage content attribute should not themselves contain additional elements—since such elements' contents are replaced each turn via their associated passage, any child elements would be lost. +Elements that include either a data-init-passage or data-passage content attribute should not themselves contain additional elements—since such elements' contents are replaced each turn via their associated passage, any child elements would be lost.

#### History: * `v2.18.0`: Introduced. * `v2.28.0`: Added processing of the `data-passage` content attribute. +* `v2.36.0`: Added processing of the `data-init-passage` content attribute. #### Examples: @@ -141,11 +152,11 @@ Elements that include a data-passage content attribute should n
``` -##### With `data-passage` content attributes +##### With `data-init-passage` and `data-passage` content attributes ```
- +
@@ -256,6 +267,20 @@ Does not affect script or stylesheet tagged passages, +### `init` {#special-tag-init} + +Registers the passage as an initialization passage. Used for pre-story-start initialization tasks, like variable initialization (happens at the beginning of story initialization). Generates no output. + +

Note: +This is chiefly intended for use by add-ons/libraries. For normal projects, authors are encouraged to continue to use the StoryInit special named passage. +

+ +#### History: + +* `v2.36.0`: Introduced. + + + ### `script` {#special-tag-script} **Twine 2:** *Not special.* Use the *Edit Story JavaScript* story editor menu item for scripts. @@ -346,13 +371,23 @@ Alias for `jQuery`, by default. **NOTE:** This should not be confused with [sto -### `$args` {#special-variable-dollar-args} +### `_args` {#special-variable-underscore-args} Widget arguments array (only inside widgets). See [`<>`](#macros-macro-widget) for more information. #### History: -* `v2.0.0`: Introduced. +* `v2.36.0`: Introduced. + + + +### `_contents` {#special-variable-underscore-contents} + +Widget contents string (only inside block widgets). See [`<>`](#macros-macro-widget) for more information. + +#### History: + +* `v2.36.0`: Introduced. @@ -545,6 +580,19 @@ UIBar API. See [`UIBar` API](#uibar-api) for more information. +### `$args` {#special-variable-dollar-args} + +

Deprecated: +The $args special variable has been deprecated and should no longer be used. See the _args special variable for its replacement. +

+ +#### History: + +* `v2.0.0`: Introduced. +* `v2.36.0`: Deprecated. + + + ### `postdisplay` {#special-variable-postdisplay}

Deprecated: @@ -553,7 +601,7 @@ UIBar API. See [`UIBar` API](#uibar-api) for more information. #### History: -* `v2.0.0`: Basic support. +* `v2.0.0`: Introduced. * `v2.31.0`: Deprecated. @@ -566,7 +614,7 @@ UIBar API. See [`UIBar` API](#uibar-api) for more information. #### History: -* `v2.0.0`: Basic support. +* `v2.0.0`: Introduced. * `v2.31.0`: Deprecated. @@ -579,7 +627,7 @@ UIBar API. See [`UIBar` API](#uibar-api) for more information. #### History: -* `v2.0.0`: Basic support. +* `v2.0.0`: Introduced. * `v2.31.0`: Deprecated. @@ -592,7 +640,7 @@ UIBar API. See [`UIBar` API](#uibar-api) for more information. #### History: -* `v2.0.0`: Basic support. +* `v2.0.0`: Introduced. * `v2.31.0`: Deprecated. @@ -605,5 +653,5 @@ UIBar API. See [`UIBar` API](#uibar-api) for more information. #### History: -* `v2.0.0`: Basic support. +* `v2.0.0`: Introduced. * `v2.31.0`: Deprecated. diff --git a/docs/core/twinescript.md b/docs/core/twinescript.md index adda491..0c9ec6d 100644 --- a/docs/core/twinescript.md +++ b/docs/core/twinescript.md @@ -85,21 +85,24 @@ 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. -

Warning: -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). -

+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.
Warning: -

Functions, including non-instance methods, are not directly supported within story variables because of a few issues.

-
    -
  1. A function's scope cannot be restored. Thus, if your function depends upon its scope, then it will not work properly when revived from sessions or saves.
  2. -
  3. Function behavior is immutable. Thus, storing them within story variables is generally wasteful.
  4. -
-

Methods of class instances are not affected by either issue, as they're never actually stored within story variables, being on their classes' prototypes.

+

Due to how SugarCube stores the state history a few constructs are not supported within story variables.

+
    +
  • Circular references. If you need them, then you'll need to keep them out of story variables.
  • +
  • Property attributes, including getters/setters, and symbol properties. If you need them, then you'll need to use a class or similar non-generic object.
  • +
  • +

    Functions, including static—i.e., non-instance—methods, due to a few issues.

    +
      +
    1. A function's scope cannot be restored. Thus, if your function depends upon its scope, then it will not work properly when revived from sessions or saves.
    2. +
    3. Function behavior is immutable. Thus, storing them within story variables is generally wasteful.
    4. +
    +

    Instance methods of classes are not affected by either issue, as they're never actually stored within story variables, being referenced from their classes' prototypes instead.

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