Skip to content

Commit 0f08d2e

Browse files
authored
RELEASE: v2.33.2
* 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.
2 parents ba1a16e + 57f37d5 commit 0f08d2e

File tree

7 files changed

+147
-114
lines changed

7 files changed

+147
-114
lines changed

dist/format.js

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

docs/api/api-dialog.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ Call this only after populating the dialog with content.
117117
An options object should have some of the following properties:
118118

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

122121
#### Examples:
123122

package.json

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

src/engine.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ var Engine = (() => { // eslint-disable-line no-unused-vars, no-var
124124
.appendTo(document.head)
125125
.get(0) // return the <style> element itself
126126
)());
127+
_hideOutlines(); // initially hide outlines
127128
let _lastOutlineEvent;
128129
jQuery(document).on(
129130
'mousedown.aria-outlines keydown.aria-outlines',
@@ -636,7 +637,6 @@ var Engine = (() => { // eslint-disable-line no-unused-vars, no-var
636637
}
637638

638639
// Last second post-processing for accessibility and other things.
639-
_hideOutlines(); // initially hide outlines
640640
jQuery('#story')
641641
// Add `link-external` to all `href` bearing `<a>` elements which don't have it.
642642
.find('a[href]:not(.link-external)')

src/lib/diff.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var Diff = (() => { // eslint-disable-line no-unused-vars, no-var
2525
});
2626

2727
/*
28-
Returns a difference object generated from comparing the the orig and dest objects.
28+
Returns a difference object generated from comparing the orig and dest objects.
2929
*/
3030
function diff(orig, dest) /* diff object */ {
3131
const objToString = Object.prototype.toString;

src/macros/macrolib.js

Lines changed: 139 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@
389389
Macro.add('type', {
390390
isAsync : true,
391391
tags : null,
392+
typeId : 0,
392393

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

539+
// Generate our unique ID.
540+
const selfId = ++this.self.typeId;
541+
538542
// Push our typing handler onto the queue.
539-
TempState.macroTypeQueue.push(() => {
540-
const $wrapper = jQuery(document.createElement(elTag))
541-
.addClass(className);
543+
TempState.macroTypeQueue.push({
544+
id : selfId,
542545

543-
// Add the user ID, if any.
544-
if (elId) {
545-
$wrapper.attr('id', elId);
546-
}
546+
handler() {
547+
const $wrapper = jQuery(document.createElement(elTag))
548+
.addClass(className);
547549

548-
// Add the user class(es), if any.
549-
if (elClass) {
550-
$wrapper.addClass(elClass);
551-
}
550+
// Add the user ID, if any.
551+
if (elId) {
552+
$wrapper.attr('id', elId);
553+
}
552554

553-
new Wikifier($wrapper, contents);
555+
// Add the user class(es), if any.
556+
if (elClass) {
557+
$wrapper.addClass(elClass);
558+
}
554559

555-
const passage = State.passage;
560+
// Wikify the contents into `$wrapper`.
561+
new Wikifier($wrapper, contents);
556562

557-
// Skip typing if….
558-
if (
559-
// …we've visited the passage before.
560-
!Config.macros.typeVisitedPassages
561-
&& State.passages.slice(0, -1).some(title => title === passage)
563+
// Cache info about the current turn.
564+
const passage = State.passage;
565+
const turn = State.turns;
562566

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

568-
// Remove this handler from the queue.
569-
TempState.macroTypeQueue.shift();
573+
// …there were any content errors.
574+
|| $wrapper.find('.error').length > 0
575+
) {
576+
$target.replaceWith($wrapper);
570577

571-
// Run the next typing handler in the queue, if any.
572-
if (TempState.macroTypeQueue.length > 0) {
573-
TempState.macroTypeQueue.first()();
574-
}
578+
// Remove this handler from the queue.
579+
TempState.macroTypeQueue.shift();
575580

576-
return;
577-
}
578-
579-
// Create a new `NodeTyper` instance for the wrapper's contents and
580-
// replace the target with the typing wrapper.
581-
const typer = new NodeTyper({
582-
targetNode : $wrapper.get(0),
583-
classNames : cursor === 'none' ? null : `${className}-cursor`
584-
});
585-
$target.replaceWith($wrapper);
586-
587-
// Set up event IDs.
588-
const typingCompleteId = ':typingcomplete';
589-
const typingStartId = ':typingstart';
590-
const typingStopId = ':typingstop';
591-
const keydownAndNS = `keydown${namespace}`;
592-
const typingStopAndNS = `${typingStopId}${namespace}`;
593-
594-
// Set up handlers for spacebar aborting and continuations.
595-
$(document)
596-
.off(keydownAndNS)
597-
.on(keydownAndNS, ev => {
598-
// Finish typing if the player aborts via the skip key.
599-
if (
600-
Util.scrubEventKey(ev.key) === skipKey
601-
&& (ev.target === document.body || ev.target === document.documentElement)
602-
) {
603-
ev.preventDefault();
604-
$(document).off(keydownAndNS);
605-
typer.finish();
606-
}
607-
})
608-
.one(typingStopAndNS, () => {
609-
// Fire the typing complete event and return, if the queue is empty.
610-
if (TempState.macroTypeQueue.length === 0) {
611-
jQuery.event.trigger(typingCompleteId);
612-
return;
581+
// Run the next typing handler in the queue, if any.
582+
if (TempState.macroTypeQueue.length > 0) {
583+
TempState.macroTypeQueue.first().handler();
613584
}
614585

615-
// Run the next typing handler in the queue.
616-
TempState.macroTypeQueue.first()();
617-
});
618-
619-
// Set up the typing interval and start/stop event firing.
620-
const typeNode = function typeNode() {
621-
// Fire the typing start event.
622-
$wrapper.trigger(typingStartId);
586+
// Exit.
587+
return;
588+
}
623589

624-
const typeNodeId = setInterval(() => {
625-
// Stop typing if….
626-
if (
627-
// …we've navigated away.
628-
State.passage !== passage
590+
// Create a new `NodeTyper` instance for the wrapper's contents and
591+
// replace the target with the typing wrapper.
592+
const typer = new NodeTyper({
593+
targetNode : $wrapper.get(0),
594+
classNames : cursor === 'none' ? null : `${className}-cursor`
595+
});
596+
$target.replaceWith($wrapper);
629597

630-
// …we're done typing.
631-
|| !typer.type()
632-
) {
633-
clearInterval(typeNodeId);
598+
// Set up event IDs.
599+
const typingCompleteId = ':typingcomplete';
600+
const typingStartId = ':typingstart';
601+
const typingStopId = ':typingstop';
602+
const keydownAndNS = `keydown${namespace}`;
603+
const typingStopAndNS = `${typingStopId}${namespace}`;
604+
605+
// Set up handlers for spacebar aborting and continuations.
606+
$(document)
607+
.off(keydownAndNS)
608+
.on(keydownAndNS, ev => {
609+
// Finish typing if the player aborts via the skip key.
610+
if (
611+
Util.scrubEventKey(ev.key) === skipKey
612+
&& (ev.target === document.body || ev.target === document.documentElement)
613+
) {
614+
ev.preventDefault();
615+
$(document).off(keydownAndNS);
616+
typer.finish();
617+
}
618+
})
619+
.one(typingStopAndNS, () => {
620+
if (TempState.macroTypeQueue) {
621+
// If the queue is empty, fire the typing complete event.
622+
if (TempState.macroTypeQueue.length === 0) {
623+
jQuery.event.trigger(typingCompleteId);
624+
}
625+
// Elsewise, run the next typing handler in the queue.
626+
else {
627+
TempState.macroTypeQueue.first().handler();
628+
}
629+
}
630+
});
634631

635-
// Remove this handler from the queue.
636-
TempState.macroTypeQueue.shift();
632+
// Set up the typing interval and start/stop event firing.
633+
const typeNode = function typeNode() {
634+
// Fire the typing start event.
635+
$wrapper.trigger(typingStartId);
636+
637+
const typeNodeId = setInterval(() => {
638+
// Stop typing if….
639+
if (
640+
// …we've navigated away.
641+
State.passage !== passage
642+
|| State.turns !== turn
643+
644+
// …we're done typing.
645+
|| !typer.type()
646+
) {
647+
// Terminate the timer.
648+
clearInterval(typeNodeId);
649+
650+
// Remove this handler from the queue, if the queue still exists and the
651+
// handler IDs match.
652+
if (
653+
TempState.macroTypeQueue
654+
&& TempState.macroTypeQueue.length > 0
655+
&& TempState.macroTypeQueue.first().id === selfId
656+
) {
657+
TempState.macroTypeQueue.shift();
658+
}
637659

638-
// Fire the typing stop event.
639-
$wrapper.trigger(typingStopId);
660+
// Fire the typing stop event.
661+
$wrapper.trigger(typingStopId);
640662

641-
// Add the done class to the wrapper.
642-
$wrapper.addClass(`${className}-done`);
663+
// Add the done class to the wrapper.
664+
$wrapper.addClass(`${className}-done`);
643665

644-
// Add the cursor class to the wrapper, if we're keeping it.
645-
if (cursor === 'keep') {
646-
$wrapper.addClass(`${className}-cursor`);
666+
// Add the cursor class to the wrapper, if we're keeping it.
667+
if (cursor === 'keep') {
668+
$wrapper.addClass(`${className}-cursor`);
669+
}
647670
}
648-
}
649-
}, speed);
650-
};
671+
}, speed);
672+
};
651673

652-
// Kick off typing the node.
653-
if (start) {
654-
setTimeout(typeNode, start);
655-
}
656-
else {
657-
typeNode();
674+
// Kick off typing the node.
675+
if (start) {
676+
setTimeout(typeNode, start);
677+
}
678+
else {
679+
typeNode();
680+
}
658681
}
659682
});
660683

661684
// If we're to start typing, then either set up a `:passageend` event handler
662685
// to do so or start it immediately, depending on the engine state.
663686
if (startTyping) {
664687
if (Engine.isPlaying()) {
665-
$(document).one(`:passageend${namespace}`, () => TempState.macroTypeQueue.first()());
688+
$(document).one(`:passageend${namespace}`, () => TempState.macroTypeQueue.first().handler());
666689
}
667690
else {
668-
TempState.macroTypeQueue.first()();
691+
TempState.macroTypeQueue.first().handler();
669692
}
670693
}
671694
}
@@ -3365,14 +3388,18 @@
33653388
throw new TypeError('callback parameter must be a function');
33663389
}
33673390

3368-
const turnId = State.turns;
3391+
// Cache info about the current turn.
3392+
const passage = State.passage;
3393+
const turn = State.turns;
3394+
3395+
// Timer info.
33693396
const timers = this.timers;
33703397
let timerId = null;
33713398

33723399
// Set up the interval.
33733400
timerId = setInterval(() => {
3374-
// Terminate the timer if the turn IDs do not match.
3375-
if (turnId !== State.turns) {
3401+
// Terminate if we've navigated away.
3402+
if (State.passage !== passage || State.turns !== turn) {
33763403
clearInterval(timerId);
33773404
timers.delete(timerId);
33783405
return;
@@ -3539,7 +3566,11 @@
35393566
throw new TypeError('callback parameter must be a function');
35403567
}
35413568

3542-
const turnId = State.turns;
3569+
// Cache info about the current turn.
3570+
const passage = State.passage;
3571+
const turn = State.turns;
3572+
3573+
// Timer info.
35433574
const timers = this.timers;
35443575
let timerId = null;
35453576
let nextItem = items.shift();
@@ -3548,7 +3579,8 @@
35483579
// Bookkeeping.
35493580
timers.delete(timerId);
35503581

3551-
if (turnId !== State.turns) {
3582+
// Terminate if we've navigated away.
3583+
if (State.passage !== passage || State.turns !== turn) {
35523584
return;
35533585
}
35543586

src/ui.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
202202
}
203203

204204
function uiBuildSaves() {
205+
const savesAllowed = typeof Config.saves.isAllowed !== 'function' || Config.saves.isAllowed();
206+
205207
function createActionItem(bId, bClass, bText, bAction) {
206208
const $btn = jQuery(document.createElement('button'))
207209
.attr('id', `saves-${bId}`)
@@ -363,7 +365,7 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
363365
else {
364366
// Add the save button.
365367
$tdLoad.append(
366-
createButton('save', 'ui-close', L10n.get('savesLabelSave'), i, Save.slots.save)
368+
createButton('save', 'ui-close', L10n.get('savesLabelSave'), i, savesAllowed ? Save.slots.save : null)
367369
);
368370

369371
// Add the description.
@@ -409,7 +411,7 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
409411
'export',
410412
'ui-close',
411413
L10n.get('savesLabelExport'),
412-
() => Save.export()
414+
savesAllowed ? () => Save.export() : null
413415
));
414416
$btnBar.append(createActionItem(
415417
'import',

0 commit comments

Comments
 (0)