Skip to content

Commit

Permalink
RELEASE: v2.33.2
Browse files Browse the repository at this point in the history
* Fixed an issue where the `<<type>>` macro could throw an error if the player navigated away while it was still typing.
* Fixed an issue where WAI-ARIA focus outlines could be lost when navigating.
* Fixed an issue in the `Dialog` API documentation.
* Updated the Saves dialog to disable save buttons if the `Config.saves.isAllowed` query yields `false`.
* Minor internal improvement to the `<<repeat>>` and `<<timed>>` macros.
  • Loading branch information
tmedwards authored Aug 14, 2020
2 parents ba1a16e + 57f37d5 commit 0f08d2e
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 114 deletions.
2 changes: 1 addition & 1 deletion dist/format.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion docs/api/api-dialog.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ Call this only after populating the dialog with content.
An options object should have some of the following properties:

* **`top`:** Top y-coordinate of the dialog (default: `50`; in pixels, but without the unit).
* **`opacity`:** Opacity of the overlay (default: `0.8`).

#### Examples:

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.1",
"version": "2.33.2",
"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
2 changes: 1 addition & 1 deletion src/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ var Engine = (() => { // eslint-disable-line no-unused-vars, no-var
.appendTo(document.head)
.get(0) // return the <style> element itself
)());
_hideOutlines(); // initially hide outlines
let _lastOutlineEvent;
jQuery(document).on(
'mousedown.aria-outlines keydown.aria-outlines',
Expand Down Expand Up @@ -636,7 +637,6 @@ var Engine = (() => { // eslint-disable-line no-unused-vars, no-var
}

// Last second post-processing for accessibility and other things.
_hideOutlines(); // initially hide outlines
jQuery('#story')
// Add `link-external` to all `href` bearing `<a>` elements which don't have it.
.find('a[href]:not(.link-external)')
Expand Down
2 changes: 1 addition & 1 deletion src/lib/diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var Diff = (() => { // eslint-disable-line no-unused-vars, no-var
});

/*
Returns a difference object generated from comparing the the orig and dest objects.
Returns a difference object generated from comparing the orig and dest objects.
*/
function diff(orig, dest) /* diff object */ {
const objToString = Object.prototype.toString;
Expand Down
246 changes: 139 additions & 107 deletions src/macros/macrolib.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@
Macro.add('type', {
isAsync : true,
tags : null,
typeId : 0,

handler() {
if (this.args.length === 0) {
Expand Down Expand Up @@ -535,137 +536,159 @@
// If the queue is empty at this point, set the start typing flag.
const startTyping = TempState.macroTypeQueue.length === 0;

// Generate our unique ID.
const selfId = ++this.self.typeId;

// Push our typing handler onto the queue.
TempState.macroTypeQueue.push(() => {
const $wrapper = jQuery(document.createElement(elTag))
.addClass(className);
TempState.macroTypeQueue.push({
id : selfId,

// Add the user ID, if any.
if (elId) {
$wrapper.attr('id', elId);
}
handler() {
const $wrapper = jQuery(document.createElement(elTag))
.addClass(className);

// Add the user class(es), if any.
if (elClass) {
$wrapper.addClass(elClass);
}
// Add the user ID, if any.
if (elId) {
$wrapper.attr('id', elId);
}

new Wikifier($wrapper, contents);
// Add the user class(es), if any.
if (elClass) {
$wrapper.addClass(elClass);
}

const passage = State.passage;
// Wikify the contents into `$wrapper`.
new Wikifier($wrapper, contents);

// Skip typing if….
if (
// …we've visited the passage before.
!Config.macros.typeVisitedPassages
&& State.passages.slice(0, -1).some(title => title === passage)
// Cache info about the current turn.
const passage = State.passage;
const turn = State.turns;

// …there were any content errors.
|| $wrapper.find('.error').length > 0
) {
$target.replaceWith($wrapper);
// Skip typing if….
if (
// …we've visited the passage before.
!Config.macros.typeVisitedPassages
&& State.passages.slice(0, -1).some(title => title === passage)

// Remove this handler from the queue.
TempState.macroTypeQueue.shift();
// …there were any content errors.
|| $wrapper.find('.error').length > 0
) {
$target.replaceWith($wrapper);

// Run the next typing handler in the queue, if any.
if (TempState.macroTypeQueue.length > 0) {
TempState.macroTypeQueue.first()();
}
// Remove this handler from the queue.
TempState.macroTypeQueue.shift();

return;
}

// Create a new `NodeTyper` instance for the wrapper's contents and
// replace the target with the typing wrapper.
const typer = new NodeTyper({
targetNode : $wrapper.get(0),
classNames : cursor === 'none' ? null : `${className}-cursor`
});
$target.replaceWith($wrapper);

// Set up event IDs.
const typingCompleteId = ':typingcomplete';
const typingStartId = ':typingstart';
const typingStopId = ':typingstop';
const keydownAndNS = `keydown${namespace}`;
const typingStopAndNS = `${typingStopId}${namespace}`;

// Set up handlers for spacebar aborting and continuations.
$(document)
.off(keydownAndNS)
.on(keydownAndNS, ev => {
// Finish typing if the player aborts via the skip key.
if (
Util.scrubEventKey(ev.key) === skipKey
&& (ev.target === document.body || ev.target === document.documentElement)
) {
ev.preventDefault();
$(document).off(keydownAndNS);
typer.finish();
}
})
.one(typingStopAndNS, () => {
// Fire the typing complete event and return, if the queue is empty.
if (TempState.macroTypeQueue.length === 0) {
jQuery.event.trigger(typingCompleteId);
return;
// Run the next typing handler in the queue, if any.
if (TempState.macroTypeQueue.length > 0) {
TempState.macroTypeQueue.first().handler();
}

// Run the next typing handler in the queue.
TempState.macroTypeQueue.first()();
});

// Set up the typing interval and start/stop event firing.
const typeNode = function typeNode() {
// Fire the typing start event.
$wrapper.trigger(typingStartId);
// Exit.
return;
}

const typeNodeId = setInterval(() => {
// Stop typing if….
if (
// …we've navigated away.
State.passage !== passage
// Create a new `NodeTyper` instance for the wrapper's contents and
// replace the target with the typing wrapper.
const typer = new NodeTyper({
targetNode : $wrapper.get(0),
classNames : cursor === 'none' ? null : `${className}-cursor`
});
$target.replaceWith($wrapper);

// …we're done typing.
|| !typer.type()
) {
clearInterval(typeNodeId);
// Set up event IDs.
const typingCompleteId = ':typingcomplete';
const typingStartId = ':typingstart';
const typingStopId = ':typingstop';
const keydownAndNS = `keydown${namespace}`;
const typingStopAndNS = `${typingStopId}${namespace}`;

// Set up handlers for spacebar aborting and continuations.
$(document)
.off(keydownAndNS)
.on(keydownAndNS, ev => {
// Finish typing if the player aborts via the skip key.
if (
Util.scrubEventKey(ev.key) === skipKey
&& (ev.target === document.body || ev.target === document.documentElement)
) {
ev.preventDefault();
$(document).off(keydownAndNS);
typer.finish();
}
})
.one(typingStopAndNS, () => {
if (TempState.macroTypeQueue) {
// If the queue is empty, fire the typing complete event.
if (TempState.macroTypeQueue.length === 0) {
jQuery.event.trigger(typingCompleteId);
}
// Elsewise, run the next typing handler in the queue.
else {
TempState.macroTypeQueue.first().handler();
}
}
});

// Remove this handler from the queue.
TempState.macroTypeQueue.shift();
// Set up the typing interval and start/stop event firing.
const typeNode = function typeNode() {
// Fire the typing start event.
$wrapper.trigger(typingStartId);

const typeNodeId = setInterval(() => {
// Stop typing if….
if (
// …we've navigated away.
State.passage !== passage
|| State.turns !== turn

// …we're done typing.
|| !typer.type()
) {
// Terminate the timer.
clearInterval(typeNodeId);

// Remove this handler from the queue, if the queue still exists and the
// handler IDs match.
if (
TempState.macroTypeQueue
&& TempState.macroTypeQueue.length > 0
&& TempState.macroTypeQueue.first().id === selfId
) {
TempState.macroTypeQueue.shift();
}

// Fire the typing stop event.
$wrapper.trigger(typingStopId);
// Fire the typing stop event.
$wrapper.trigger(typingStopId);

// Add the done class to the wrapper.
$wrapper.addClass(`${className}-done`);
// Add the done class to the wrapper.
$wrapper.addClass(`${className}-done`);

// Add the cursor class to the wrapper, if we're keeping it.
if (cursor === 'keep') {
$wrapper.addClass(`${className}-cursor`);
// Add the cursor class to the wrapper, if we're keeping it.
if (cursor === 'keep') {
$wrapper.addClass(`${className}-cursor`);
}
}
}
}, speed);
};
}, speed);
};

// Kick off typing the node.
if (start) {
setTimeout(typeNode, start);
}
else {
typeNode();
// Kick off typing the node.
if (start) {
setTimeout(typeNode, start);
}
else {
typeNode();
}
}
});

// If we're to start typing, then either set up a `:passageend` event handler
// to do so or start it immediately, depending on the engine state.
if (startTyping) {
if (Engine.isPlaying()) {
$(document).one(`:passageend${namespace}`, () => TempState.macroTypeQueue.first()());
$(document).one(`:passageend${namespace}`, () => TempState.macroTypeQueue.first().handler());
}
else {
TempState.macroTypeQueue.first()();
TempState.macroTypeQueue.first().handler();
}
}
}
Expand Down Expand Up @@ -3365,14 +3388,18 @@
throw new TypeError('callback parameter must be a function');
}

const turnId = State.turns;
// Cache info about the current turn.
const passage = State.passage;
const turn = State.turns;

// Timer info.
const timers = this.timers;
let timerId = null;

// Set up the interval.
timerId = setInterval(() => {
// Terminate the timer if the turn IDs do not match.
if (turnId !== State.turns) {
// Terminate if we've navigated away.
if (State.passage !== passage || State.turns !== turn) {
clearInterval(timerId);
timers.delete(timerId);
return;
Expand Down Expand Up @@ -3539,7 +3566,11 @@
throw new TypeError('callback parameter must be a function');
}

const turnId = State.turns;
// Cache info about the current turn.
const passage = State.passage;
const turn = State.turns;

// Timer info.
const timers = this.timers;
let timerId = null;
let nextItem = items.shift();
Expand All @@ -3548,7 +3579,8 @@
// Bookkeeping.
timers.delete(timerId);

if (turnId !== State.turns) {
// Terminate if we've navigated away.
if (State.passage !== passage || State.turns !== turn) {
return;
}

Expand Down
6 changes: 4 additions & 2 deletions src/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
}

function uiBuildSaves() {
const savesAllowed = typeof Config.saves.isAllowed !== 'function' || Config.saves.isAllowed();

function createActionItem(bId, bClass, bText, bAction) {
const $btn = jQuery(document.createElement('button'))
.attr('id', `saves-${bId}`)
Expand Down Expand Up @@ -363,7 +365,7 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
else {
// Add the save button.
$tdLoad.append(
createButton('save', 'ui-close', L10n.get('savesLabelSave'), i, Save.slots.save)
createButton('save', 'ui-close', L10n.get('savesLabelSave'), i, savesAllowed ? Save.slots.save : null)
);

// Add the description.
Expand Down Expand Up @@ -409,7 +411,7 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
'export',
'ui-close',
L10n.get('savesLabelExport'),
() => Save.export()
savesAllowed ? () => Save.export() : null
));
$btnBar.append(createActionItem(
'import',
Expand Down

0 comments on commit 0f08d2e

Please sign in to comment.