Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save cyrusfirheir/c8b47ca3af805964b5ebe1d51f5a7d2e to your computer and use it in GitHub Desktop.

Select an option

Save cyrusfirheir/c8b47ca3af805964b5ebe1d51f5a7d2e to your computer and use it in GitHub Desktop.
CTP macro-set for SugarCube 2

Overview

This set of macros/functions aims to provide an easy way to set up content that is revealed bit-by-bit via user interaction.

Using nested <<linkreplace>> and <<linkappend>> works, but gets tedious and is often prone to errors. The CTP (Click To Proceed: original-est name ever) macros make it a bit easier by turning them into blocks instead of nests.

Installation

If using the Twine desktop/web app, copy contents of CTP.js to Story JavaScript, and contents of CTP.css to Story Stylesheet.

If using a compiler like Tweego, drop CTP.js and CTP.css to your source folder.

Example Usage

<<ctp "testID">>
  This is the first string.
<<ctpNext clear>>
  Second! It cleared the first one out!
<<ctpNext nobr>>
  Third, but with nobr...
<<ctpNext 2s>>
  The fourth shows up 2 seconds late.
<<ctpNext t8n>>
  And the final one. With a transition!
<</ctp>>

<<link "Next">>
  <<ctpAdvance "testID">>
<</link>>

<<link "Back">>
  <<ctpBack "testID">>
<</link>>

Macros

<<ctp "id" [keywords]>>

  • id: (string) Unique ID to be used to identify the chain of content. The naming rule follows the same as those of SugarCube variable names (learn more here).

  • keywords: (optional|string) The following keywords can be used to alter the behavior of the macro throughout the entire chain. These keywords apply to all blocks:

    • clear: Clears the content of the previous block. Use for replacing.
    • nobr: Appends content in the same line as the last block instead of going to a new line.
    • t8n or transition: Custom CSS animation based transition (250ms fade-in by default).
    • (delay): A valid CSS time value (e.g. 3s or 250ms) to delay the display of the block by.

Example:

<<ctp "ID_of_the_year">>
  Bare minimum...
<</ctp>>

<<ctpNext [keywords]>>

To be used inside <<ctp>> to separate the content into blocks.

  • keywords: (optional|string) The following keywords can be used to alter the behavior of the macro for the current block:
    • clear: Clears the content of the previous block. Use for replacing.
    • nobr: Appends content in the same line as last block instead of going to a new line.
    • t8n or transition: Custom CSS animation based transition (250ms fade-in by default).
    • (delay): A valid CSS time value (e.g. 3s or 250ms) to delay the display of the block by. This delay overrides the delay specified in <<ctp>>.

Example:

<<ctp "fancyCTP">>
  One.
<<ctpNext clear>>
  Two with clear.
<<ctpNext nobr>>
  Three on the same line.
<<ctpNext 500ms>>
  Delayed four.
<<ctpNext t8n>>
  Fading five.
<</ctp>>

<<ctpAdvance "id">>

The 'proceed' part of Click To Proceed... Used to move the train forward and show the next blocks.

  • id: (string) Unique ID which was set up in <<ctp>>.

NOTE: Use with user interaction (inside a <<link>> or <<button>>) or inside a <<timed>> macro to ensure the DOM is loaded and has the element on the page for the macro to target.

Example:

<<ctp "ID_of_the_year_once_again">>
  <!-- stuff -->
<</ctp>>

<<link "Next">>
  <<ctpAdvance "ID_of_the_year_once_again">>
<</link>>

<<ctpBack "id">>

Turns back time and goes back one block.

  • id: (string) Unique ID which was set up in <<ctp>>.

NOTE: Use with user interaction (inside a <<link>> or <<button>>) or inside a <<timed>> macro to ensure the DOM is loaded and has the element on the page for the macro to target.

Example:

<<ctp "ID_of_the_year_yet_again">>
  <!-- stuff -->
<</ctp>>

<<link "Back">>
  <<ctpBack "ID_of_the_year_yet_again">>
<</link>>

JavaScript usage - The CTP object

The CTP custom object is set up as follows:

id: (string) Unique ID.

selector: (string) CSS selector to target to output to. When used by the macro, this is the slugified form of id.

Example:

var ctpTest = new CTP({
  id: "ctpTest",
  selector: "#ctp-test-id"
});

Other properties which are used under the hood:

stack: (array) Contains the content of all blocks.

log: (object) Keeps track of blocks and their behaviors:

  • index: (whole number) Current index of block (zero-based).
  • delayed: (boolean) Whether the current block is delayed or not.

Object methods:

CTP.add(content [, keywords])

Adds content to the end of the stack and returns the CTP object for chaining.

  • content: (string) The actual content in the block.
  • keywords: (optional|string) Space-separated list of keywords (clear, nobr, t8n, transition) to modify the behavior of the blocks.

Example:

ctpTest
  .add("This is the first string.")
  .add("Second! It cleared the first one out!", "clear")
  .add("Third, but with nobr...", "nobr")
  .add("And the final one. With a transition!", "t8n");

CTP.advance()

Does the same as <<ctpAdvance>>, moving to the next block. Returns the CTP object for chaining.

Example:

ctpTest.advance();

CTP.back()

Does the same as <<ctpBack>>, reverting to the previous blocks. Returns the CTP object for chaining.

Example:

ctpTest.back();

CTP.ctpEntry(index [, noT8n])

Returns the HTML output for a single block at the index passed into it.

  • index: (whole number) Index of block to return.
  • noT8n: (optional|boolean) If true, all transitions are removed. False by default.

Example:

ctpTest.ctpEntry(2);

// Assuming ctpTest is the same as in the previous examples, this returns:
// <span class="macro-ctp-entry macro-ctp-entry-index-2">Third, but with nobr...</span>

CTP.out([keywords])

Returns the HTML output for the entire chain from the last 'clear' to the current index.

  • keywords: (optional|string) Space-separated list of words to alter the behavior of the output:
    • noClear: Renders all the blocks from start to finish without considering if anything was cleared in between.
    • noT8n: Removes all transitions.

Example:

// Assuming current index is 3
ctpTest.out()

/* Returns:
 *
 * <span class="macro-ctp-entry macro-ctp-entry-index-1">Second! It cleared the first one out!</span>
 * <span class="macro-ctp-entry macro-ctp-entry-index-2">Third, but with nobr...</span>
 * <br>
 * <span class="macro-ctp-entry macro-ctp-entry-index-3">And the final one. With a transition!</span>
 */

Complete usage:

JavaScript:

State.variables.ctpTest = new CTP({
  id: "ctpTest",
  selector: "#ctp-test-id"
});

State.variables.ctpTest
  .add("This is the first string.")
  .add("Second! It cleared the first one out!", "clear")
  .add("Third, but with nobr...", "nobr")
  .add("And the final one. With a transition!", "t8n");

In Passage:

<div id="#ctp-test-id">
  <<= $ctpTest.out()>>
</div>

<<link "Advance">>
  <<run $ctpTest.advance()>>
  <!-- Because $ctpTest was created manually, using the <<ctpAdvance>> macro won't work. To be able to use <<ctpAdvance>>, the CTP object needs to be set as a property of State.variables["#macro-ctp-dump"] as that is what is used internally to store CTP objects created via the macros. -->
<</link>>

.macro-ctp-entry-t8n {
opacity: 1;
animation: macro-ctp-fade-in 0.25s ease;
}
@keyframes macro-ctp-fade-in {
0% {
opacity: 0;
}
}
window.CTP = function (config) {
this.id = "";
this.selector = "";
this.stack = [];
this.log = {
index: 0,
delayed: false
};
Object.keys(config).forEach(function (pn) {
this[pn] = clone(config[pn]);
}, this);
};
CTP.prototype.clone = function () {
return new CTP(this);
};
CTP.prototype.toJSON = function () {
var ownData = {};
Object.keys(this).forEach(function (pn) {
ownData[pn] = clone(this[pn]);
}, this);
return JSON.reviveWrapper('new CTP($ReviveData$)', ownData);
};
CTP.prototype.add = function (entry) {
var mods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
mods = mods.split(/\s+/g);
this.stack.push({
index: this.stack.length,
clear: this.stack.length && mods.includes("clear"),
nobr: mods.includes("nobr"),
transition: mods.includesAny("t8n", "transition"),
delay: Util.fromCssTime(mods.find(function (el) {
return /\d+m?s/.test(el);
}) || "0s"),
content: entry
});
return this;
};
CTP.prototype.ctpEntry = function (index) {
var noT8n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (index < 0 || index >= this.stack.length) return "";
var entry = this.stack[index];
var t8n = noT8n ? "" : entry.transition ? "macro-ctp-entry-t8n" : "";
var br = index === 0 || entry.clear ? " " : entry.nobr ? " " : "<br>";
return br + '<span class="macro-ctp-entry macro-ctp-entry-index-' + index + ' ' + t8n + '">' + entry.content + '</span>';
};
CTP.prototype.out = function () {
var _this = this;
var mods = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
var noClear = mods.includes("noClear");
var noT8n = mods.includes("noT8n");
var clearIndex = 0;
if (!noClear) clearIndex = Math.max(this.stack.filter(function (el) {
return el.index <= _this.log.index && el.clear;
}, this).length, 0);
var ret = this.stack.slice(clearIndex, this.log.index + 1).reduce(function (acc, cur) {
return acc + _this.ctpEntry(cur.index, noT8n);
}, "");
return ret;
};
CTP.prototype.advance = function () {
var _this2 = this;
if (this.log.index === this.stack.length - 1 || this.log.delayed) return this;
var index = ++this.log.index;
var _el = $(this.selector);
if (this.stack[index].clear) _el.empty();
this.log.delayed = true;
function delay(ctp) {
ctp.log.delayed = false;
$(ctp.selector).wiki(ctp.ctpEntry(ctp.log.index));
}
setTimeout(function () {
return delay(_this2);
}, this.stack[index].delay);
return this;
};
CTP.prototype.back = function () {
if (this.log.index <= 0 || this.log.delayed) return this;
this.log.index--;
$(this.selector).empty().wiki(this.out("noT8n"));
return this;
};
Macro.add("ctp", {
tags: ["ctpNext"],
handler: function handler() {
var _id = this.args[0];
var _data = 'data-ctp="' + Util.escape(_id) + '"';
var ctp = new CTP({
id: _id,
selector: '[' + _data + ']'
});
var _overArgs = this.payload[0].args;
_overArgs.reverse().pop();
_overArgs = " " + _overArgs.join(" ");
this.payload.forEach(function (el, index) {
var _args = el.args;
if (el.name === "ctp") _args = [];
ctp.add(el.contents.trim(), _args.join(" ") + _overArgs);
});
$(this.output).wiki('<span ' + _data + ' class="macro-ctp-wrapper">' + ctp.out() + '</span>');
variables()["#macro-ctp-dump"] = variables()["#macro-ctp-dump"] || {};
variables()["#macro-ctp-dump"][_id] = ctp;
}
});
Macro.add("ctpAdvance", {
handler: function handler() {
var _id = this.args[0];
variables()["#macro-ctp-dump"] = variables()["#macro-ctp-dump"] || {};
var ctp = variables()["#macro-ctp-dump"][_id];
if (ctp) ctp.advance();
}
});
Macro.add("ctpBack", {
handler: function handler() {
var _id = this.args[0];
variables()["#macro-ctp-dump"] = variables()["#macro-ctp-dump"] || {};
var ctp = variables()["#macro-ctp-dump"][_id];
if (ctp) ctp.back();
}
});
$(document).on(':passageinit', function () {
delete variables()["#macro-ctp-dump"];
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment