|
389 | 389 | Macro.add('type', {
|
390 | 390 | isAsync : true,
|
391 | 391 | tags : null,
|
| 392 | + typeId : 0, |
392 | 393 |
|
393 | 394 | handler() {
|
394 | 395 | if (this.args.length === 0) {
|
|
535 | 536 | // If the queue is empty at this point, set the start typing flag.
|
536 | 537 | const startTyping = TempState.macroTypeQueue.length === 0;
|
537 | 538 |
|
| 539 | + // Generate our unique ID. |
| 540 | + const selfId = ++this.self.typeId; |
| 541 | + |
538 | 542 | // 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, |
542 | 545 |
|
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); |
547 | 549 |
|
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 | + } |
552 | 554 |
|
553 |
| - new Wikifier($wrapper, contents); |
| 555 | + // Add the user class(es), if any. |
| 556 | + if (elClass) { |
| 557 | + $wrapper.addClass(elClass); |
| 558 | + } |
554 | 559 |
|
555 |
| - const passage = State.passage; |
| 560 | + // Wikify the contents into `$wrapper`. |
| 561 | + new Wikifier($wrapper, contents); |
556 | 562 |
|
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; |
562 | 566 |
|
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) |
567 | 572 |
|
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); |
570 | 577 |
|
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(); |
575 | 580 |
|
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(); |
613 | 584 | }
|
614 | 585 |
|
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 | + } |
623 | 589 |
|
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); |
629 | 597 |
|
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 | + }); |
634 | 631 |
|
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 | + } |
637 | 659 |
|
638 |
| - // Fire the typing stop event. |
639 |
| - $wrapper.trigger(typingStopId); |
| 660 | + // Fire the typing stop event. |
| 661 | + $wrapper.trigger(typingStopId); |
640 | 662 |
|
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`); |
643 | 665 |
|
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 | + } |
647 | 670 | }
|
648 |
| - } |
649 |
| - }, speed); |
650 |
| - }; |
| 671 | + }, speed); |
| 672 | + }; |
651 | 673 |
|
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 | + } |
658 | 681 | }
|
659 | 682 | });
|
660 | 683 |
|
661 | 684 | // If we're to start typing, then either set up a `:passageend` event handler
|
662 | 685 | // to do so or start it immediately, depending on the engine state.
|
663 | 686 | if (startTyping) {
|
664 | 687 | if (Engine.isPlaying()) {
|
665 |
| - $(document).one(`:passageend${namespace}`, () => TempState.macroTypeQueue.first()()); |
| 688 | + $(document).one(`:passageend${namespace}`, () => TempState.macroTypeQueue.first().handler()); |
666 | 689 | }
|
667 | 690 | else {
|
668 |
| - TempState.macroTypeQueue.first()(); |
| 691 | + TempState.macroTypeQueue.first().handler(); |
669 | 692 | }
|
670 | 693 | }
|
671 | 694 | }
|
|
3365 | 3388 | throw new TypeError('callback parameter must be a function');
|
3366 | 3389 | }
|
3367 | 3390 |
|
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. |
3369 | 3396 | const timers = this.timers;
|
3370 | 3397 | let timerId = null;
|
3371 | 3398 |
|
3372 | 3399 | // Set up the interval.
|
3373 | 3400 | 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) { |
3376 | 3403 | clearInterval(timerId);
|
3377 | 3404 | timers.delete(timerId);
|
3378 | 3405 | return;
|
|
3539 | 3566 | throw new TypeError('callback parameter must be a function');
|
3540 | 3567 | }
|
3541 | 3568 |
|
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. |
3543 | 3574 | const timers = this.timers;
|
3544 | 3575 | let timerId = null;
|
3545 | 3576 | let nextItem = items.shift();
|
|
3548 | 3579 | // Bookkeeping.
|
3549 | 3580 | timers.delete(timerId);
|
3550 | 3581 |
|
3551 |
| - if (turnId !== State.turns) { |
| 3582 | + // Terminate if we've navigated away. |
| 3583 | + if (State.passage !== passage || State.turns !== turn) { |
3552 | 3584 | return;
|
3553 | 3585 | }
|
3554 | 3586 |
|
|
0 commit comments