Commit 5f921bee authored by Leon Arnott's avatar Leon Arnott
Browse files

Fixed (source:) not producing an error when given custom macro commands.

Also:
 * Tweaked some more documentation articles.
 * Fixed the CSS for (button:) links.
 * Updated introduction.md with a 3.2.0 introduction.
 * Found myself forced to make the childrenProbablyInline() check in transitionIn() and transitionOut() occur in a timeout, so that section expression execution could be performed, and thus changers like (box:) could provide the <tw-transition-container> with the correct display property. Also decided, to ensure .enchantment-button works correctly, that childrenProbablyInline() should actually call upon getComputedStyle() when appraising hard-to-judge elements, instead of just using the .style.display property.
parent e40ece040a6a
......@@ -46,6 +46,9 @@ define(['jquery','utils/operationutils','internaltypes/changedescriptor', 'inter
When this command is run, simply return the fully permuted CD.
*/
TwineScript_Run: () => cd,
/*
Note that since these cannot be serialised, they can't have a TwineScript_ToSource() method.
*/
});
return ret;
};
......
......@@ -52,16 +52,11 @@ define([
| `integer`, `int` | Only matches whole numbers (numbers with no fractional component, and which are positive or negative).
| `empty` | Only matches these empty structures: `""` (the empty string), `(a:)`, `(dm:)` and `(ds:)`.
| `whitespace` | Only matches a single character of whitespace (spaces, newlines, and other kinds of space).
| `...whitespace` | This is the above type combined with the spread `...` operator. Matches empty strings, or strings containing only whitespace.
| `lowercase` | Only matches a single lowercase character. Lowercase characters are characters that change when put through (uppercase:).
| `...lowercase` | This is the above type combined with the spread `...` operator. Matches empty strings, or strings containing only lowercase characters.
| `uppercase` | Only matches a single uppercase character. Uppercase characters are characters that change when put through (lowercase:).
| `...uppercase` | This is the above type combined with the spread `...` operator. Matches empty strings, or strings containing only uppercase characters.
| `anycase` | This matches any character which is case-sensitive - that is, where its (lowercase:) form doesn't match its (uppercase:) form.
| `alphanumeric`, `alnum` | Only matches a single alphanumeric character (letters and numbers).
| `...alnum`, `...alphanumeric` | This is the above type combined with the spread `...` operator. Matches empty strings, or strings containing only alphanumeric characters.
| `digit` | Only matches a string consisting of exactly one of the characters '0', '1', '2', '3', '4', '5', '6', '7', '8', and '9'.
| `...digit` | This is the above type combined with the spread `...` operator. Matches empty strings, or strings containing only digit characters.
| `linebreak` | Only matches a line break character.
| `const` | Matches nothing; Use this only with (set:) to make constants.
| `any` | Matches anything; Use this with (macro:) to make variables that accept any storable type, or with (set:) inside data structure patterns.
......@@ -80,9 +75,11 @@ define([
array, but that may not be precise enough for you. `$pos matches (a: number, number)` checks to see if $pos is an array containing only two numbers in a row. A data structure with datatype
names in various positions inside it is called a **pattern**, and `matches` is used to compare data values and patterns.
When used inside array patterns, a modified datatype called a **spread datatype** can be created using the `...` syntax. `...str` can match any number of string values inside another array.
You can think of this as a counterpart to the spread `...` syntax used inside macro calls - just as one array is turned into many values, so too is `...str` considered equivalent to enough `str` datatypes
to match the values on the other side.
A modified datatype called a **spread datatype** can be created using the `...` syntax. `...str` can match any number of string values, including zero. You can think of this as a counterpart to
the spread `...` syntax used inside macro calls - just as one array is turned into many values, so too is `...str` considered equivalent to enough `str` datatypes to match the values on the other side.
Inside a string pattern, like those created by (p:), spread datatypes have a slightly different meaning. They refer to zero or more sequences of the given datatype. `...whitespace` inside (p:) matches
an entire string of whitespace, which can be one or more characters, as well as the empty string. `...digit` matches zero or more digit characters.
Some more examples.
......@@ -90,7 +87,7 @@ define([
* `(a: 2, 3, 4) matches (a: 2, int, int)` is true. (Patterns can have exact values in them, which must be equal in the matching data).
* `(a: ...num, ...str) matches (a: 2, 3, 4, 'five')`
* `(a: (a: 2), (a: 4)) matches (a: (a: num), (a: even))` is true.
* `(p: (p-many:"A"), "!")` matches "AAAAAAAA!"` is true.
* `(p: (p-many:"A"), "!") matches "AAAAAAAA!"` is true.
To summarise, the datatype operators are the following.
......@@ -99,7 +96,7 @@ define([
| `matches` | Evaluates to boolean `true` if the data on the left matches the pattern on the right. | `(a:2,3) matches (a: num, num)`
| `is a`, `is an` | Similar to `matches`, but requires the right side to be just a type name. | `(a:2,3) is an array`, `4.1 is a number`
| `-type` | Produces a TypedVar, if a variable follows it. Note that there can't be any space between `-` and `type`. | `num-type $funds`
| `...` | Produces a spread datatype, which, when used in arrays, matches zero or more values that match the type. | `(a: ...str) matches (a:'Elf','Drow','Kithkin')`
| `...` | Produces a spread datatype, which, when used in arrays or patterns, matches zero or more values that match the type. | `(a: ...str) matches (a:'Elf','Drow','Kithkin')`
*/
let typeIndex, basicTypeIndex;
......
......@@ -51,7 +51,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
/*d:
(set: ...VariableToValue) -> Instant
Stores data values in variables.
Stores data values in variables, optionally allowing you to permanently restrict the variable to a single datatype.
Example usage:
* `(set: $battlecry to "Save a " + $favouritefood + " for me!")` creates a variable called $battlecry containing a string.
......@@ -1042,7 +1042,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
(icon-counter:)
Added in: 3.2.0.
#interface 1
#input and interface
*/
Macros.addCommand('meter',
(_, __, widthStr, labelOrGradient) => {
......@@ -1237,7 +1237,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
If only one string was given to this macro, an error will be produced.
Added in: 3.0.0
#input 1
#input and interface 1
*/
/*d:
(seq-link: [Bind], ...String) -> Command
......@@ -1279,7 +1279,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
If only one string was given to this macro, an error will be produced.
Added in: 3.2.0
#input 2
#input and interface 2
*/
[["cycling-link"],["seq-link","sequence-link"]].forEach((name, seq) => Macros.addCommand(name,
(...labels) => {
......@@ -1491,7 +1491,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
(cycling-link:), (checkbox:)
Added in: 3.0.0
#input
#input and interface
*/
Macros.addCommand("dropdown",
(_, ...labels) => {
......@@ -1634,7 +1634,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
(dropdown:), (input-box:), (confirm:)
Added in: 3.2.0
#input
#input and interface
*/
Macros.addCommand("checkbox",
() => {},
......@@ -1759,7 +1759,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
(force-input-box:), (prompt:)
Added in: 3.2.0
#input 3
#input and interface 3
*/
/*d:
(force-input-box: [Bind], String, [Number], String) -> Command
......@@ -1807,7 +1807,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
(input-box:), (prompt:), (box:)
Added in: 3.2.0
#input 4
#input and interface 4
*/
["input-box", "force-input-box"].forEach(name => Macros.addCommand(name,
(...args) => {
......
......@@ -9,6 +9,28 @@ define(['utils', 'macros', 'state', 'utils/operationutils', 'datatypes/changerco
as easily as a built-in macro.
Example usage:
The following custom macro creates a command that displays a randomly rotated hook in red, with the given opacity.
```
(set:$ghostlyLaughter to (macro: num-type _o, [
(output: )+(text-rotate:(random:0,360))+(text-colour:(hsla:0, 1, 0.5, _o))[HE HE HE]
]))
($ghostlyLaughter:) ($ghostlyLaughter:) ($ghostlyLaughter:)
```
The following custom macro creates a text string based on how many turns the player has taken. It takes no data.
```
(set: $fancyTimeName to (macro: [
(set: _timeOfDay to (history: )'s length % 24 + 1)
(output-data: (a:
"midnight", "dreamshour", "wolfshour", "dark's end", "lightbreak", "afterdawn", "early rise", "awakening",
"early warming", "joyshour", "first lunch", "shadow's end", "zenith", "shadow's birth", "second lunch", "hopeshour", "early cooling",
"lightfade", "sundown", "dark's birth", "supper", "early rest", "slumbering", "catshour"
)'s (_timeOfDay))
]))
It is now ($fancyTimeName:).
```
The following custom macro takes a datamap containing a character's attributes, and prints a line of text describing a character.
```
(set: $charSheet to (dm: "name", str, "HP", num, "poison", num, "heartbreak", bool))
(set: $healthSummary to (macro: $charSheet-type _stats, [
......
......@@ -1417,7 +1417,7 @@ define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages'
(click-undo: HookName or String) -> Command
A special shorthand combination of the (click:) and (undo:) macros, this allows you to make a hook
or bit of text into a passage link. `(click-undo: ?1)` is equivalent to `(click: ?1)[(undo:)]`
or bit of text into a passage link. `(click-undo: ?1)` is equivalent to `(click: ?1)[(undo: )]`
Example usage:
`You might have gotten yourself into a pickle that only time travel can get you out of. (click-undo: ?page)`
......
......@@ -1666,7 +1666,10 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
#styling
*/
(["background", "bg"],
(_, value) => {
(_, value) => ChangerCommand.create("background", [value]),
(d, value) => {
let property;
//Convert TwineScript CSS colours to bad old hexadecimal.
if (Colour.isPrototypeOf(value)) {
value = value.toRGBAString(value);
......@@ -1675,10 +1678,6 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
else if (Gradient.isPrototypeOf(value)) {
value = value.toLinearGradientString(value);
}
return ChangerCommand.create("background", [value]);
},
(d, value) => {
let property;
/*
Different kinds of values can be supplied to this macro
*/
......@@ -1697,7 +1696,7 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
background-size:cover allows the image to fully cover the area
without tiling, which I believe is slightly more desired.
*/
property = {"background-size": "cover", "background-image":"url(" + value + ")"};
property = {"background-size": "cover", "background-image": "url(" + value + ")"};
}
d.styles.push(property, {
/*
......@@ -2260,6 +2259,7 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
* `(box:"=XX=", 1)[Chapter complete]` produces a box that's centered, 50% of the containing element's width, and 1 line tall.
* `(box:"==X", 3)[Chapter complete]` produces a box that's right-aligned, 33% of the containing element's width, 3 lines tall.
* `(box:"X", 7)[Chapter complete]` produces a box that takes up the full containing element's width, and 7 lines tall.
* `(enchant: ?passage, (box:"XXX="))` enchants the passage, placing it in the left of the window.
Rationale:
......
......@@ -129,7 +129,7 @@ define(['macros', 'utils', 'utils/operationutils', 'datatypes/colour', 'datatype
/*d:
(source: Any) -> String
When given any data value, this will produce a string of Harlowe source code that can, when run, create
When given almost any data value, this will produce a string of Harlowe source code that can, when run, create
that value exactly.
Example usage:
......@@ -149,6 +149,8 @@ define(['macros', 'utils', 'utils/operationutils', 'datatypes/colour', 'datatype
generally be absent, so `(source: (a:2, 3, 4))` produces `"(a:2,3,4)"`. Also, if you call a macro using one if its aliases,
such as (array:) for (a:), then the source will still use its "default" name. So, `(source: (array:1))` produces `"(a:1)"`.
Commands created by custom macros (via the (output:) macro) cannot be given to this macro - attempting to do so will produce an error.
Note that you can't easily print the string returned by (source:), because, funnily enough, Harlowe will immediately
re-render it. You can use (verbatim-print:) to print it without it being rendered.
......@@ -158,7 +160,16 @@ define(['macros', 'utils', 'utils/operationutils', 'datatypes/colour', 'datatype
Added in: 3.2.0
#string
*/
("source", (_, val) => toSource(val), [Any])
("source", (_, val) => {
/*
Note that since custom macro commands cannot be serialised, they can't have a TwineScript_ToSource() method that would return this error
by itself. Also note that every built-in command has a TwineScript_ToSource() installed by Macros.
*/
if (val && val.TwineScript_TypeID === "command" && !val.TwineScript_ToSource) {
return TwineError.create("datatype", "I can't construct the source code of a command created by a custom macro.");
}
return toSource(val);
}, [Any])
/*d:
(substring: String, Number, Number) -> String
......
......@@ -9,8 +9,8 @@ define(['jquery', 'markup', 'utils/polyfills'],
// The most common block HTML tags that would be used in passage source.
"audio,blockquote,canvas,div,h1,h2,h3,h4,h5,hr,ol,p,pre,table,ul,video,"
// And the one(s) that Harlowe itself creates through its syntax.
// <tw-consecutive-br> is deliberately not included.
+ "tw-align,tw-story,tw-passage,tw-sidebar,tw-columns,tw-column,tw-meter,tw-dialog"
// <tw-consecutive-br> and <tw-dialog> are deliberately not included.
+ "tw-align,tw-story,tw-passage,tw-sidebar,tw-columns,tw-column,tw-meter"
).split(','),
usuallyInlineElements = (
......@@ -387,7 +387,7 @@ define(['jquery', 'markup', 'utils/polyfills'],
childrenProbablyInline: returns true if the matched elements probably only contain elements that
are of the 'inline' or 'none' CSS display type.
This takes some shortcuts to avoid use of the costly $css() function as much as possible,
This takes some shortcuts to avoid use of the costly getComputedStyle() function as much as possible,
hence, it can only "probably" say so.
This is used to crudely determine whether to make a <tw-transition-container> inline or block,
......@@ -399,7 +399,7 @@ define(['jquery', 'markup', 'utils/polyfills'],
so that $css() can be run on them after the first loop has returned all-true.
*/
const unknown = [];
return Array.prototype.every.call(jq.findAndFilter('*'), elem => {
return [].every.call(jq.findAndFilter('*'), elem => {
/*
If it actually has "style=display:inline", "hidden", or "style=display:none"
as an inline attribute, well, that makes life easy for us.
......@@ -433,13 +433,8 @@ define(['jquery', 'markup', 'utils/polyfills'],
unknown.push(elem);
return true;
})
/*
Since each element passed the "usually block" test, we can assume that a "" display property means it's
probably inline.
*/
&& unknown.every(elem => /none|inline|^$/.test(elem.style.display));
&& unknown.every(a => window.getComputedStyle(a).display.includes('inline'));
},
/*
Replaces oldElem with newElem while transitioning between both.
*/
......@@ -498,15 +493,12 @@ define(['jquery', 'markup', 'utils/polyfills'],
}
transitionTime = transitionTime || defaultTransitionTime(transIndex);
const childrenInline = Utils.childrenProbablyInline(el),
/*
If the element is not a tw-hook or tw-passage, we must
wrap it in a temporary element first, which can thus be
animated using CSS.
*/
mustWrap =
el.length > 1 || !childrenInline ||
!['tw-hook','tw-passage'].includes(el.tag());
/*
If the element is not a tw-hook, tw-passage or tw-expression, we must
wrap it in a temporary element first, which can thus be
animated using CSS.
*/
const mustWrap = el.length > 1 || !Utils.childrenProbablyInline(el) || !['tw-hook','tw-passage','tw-expression'].includes(el.tag());
/*
As mentioned above, we must, in some cases, wrap the nodes in containers.
*/
......@@ -526,22 +518,34 @@ define(['jquery', 'markup', 'utils/polyfills'],
'animation-delay': (transitionDelay - expedite) + "ms",
});
if (childrenInline) {
/*
If there are no element children of the container (only text), simply use 'inline'.
*/
el.css('display','inline' + (el.children().length ? '-block' : ''));
}
else {
/*
To give newly-created sections time to render and apply changers, such as (box:) to hooks,
this code, which gives the transition element the correct display property, is crudely placed in a delayed timeout.
*/
requestAnimationFrame(() => {
/*
Both of these are necessary for overriding "display:inline-block" for certain transitions,
including those that use transform.
Special case: due to <tw-backdrop> having display:flex, getComputedStyle() will always report 'display:block' for
each child element of it, even if it's a <tw-dialog>. So, ignore childrenProbablyInline() and simply assume that block positioning mustn't be applied.
Note that the other two elements that have display:flex, <tw-story> and <tw-columns>, don't have this concern due to their children
being part of usuallyBlockElements.
*/
el.css({
display:'block !important',
width:"100%",
});
}
if (el.parent().is('tw-backdrop') || Utils.childrenProbablyInline(el)) {
/*
If there are no element children of the container (only text), simply use 'inline'.
*/
el.css('display','inline' + (el.children().length ? '-block' : ''));
}
else {
/*
Both of these are necessary for overriding "display:inline-block" for certain transitions,
including those that use transform.
*/
el.css({
display:'block !important',
width:"100%",
});
}
});
/*
Each frame, reduce the delay, and potentially reduce it further if this
transition can be skipped and an input is being held.
......@@ -565,15 +569,12 @@ define(['jquery', 'markup', 'utils/polyfills'],
return;
}
transitionTime = transitionTime || defaultTransitionTime(transIndex);
const childrenInline = Utils.childrenProbablyInline(el),
/*
If the element is not a tw-hook or tw-passage, we must
wrap it in a temporary element first, which can thus be
animated using CSS.
*/
mustWrap =
el.length > 1 || !childrenInline ||
!['tw-hook','tw-passage'].includes(el.tag());
/*
If the element is not a tw-hook, tw-passage or tw-expression, we must
wrap it in a temporary element first, which can thus be
animated using CSS.
*/
const mustWrap = el.length > 1 || !Utils.childrenProbablyInline(el) || !['tw-hook','tw-passage','tw-expression'].includes(el.tag());
/*
As mentioned above, we must, in some cases, wrap the nodes in containers.
*/
......@@ -582,7 +583,7 @@ define(['jquery', 'markup', 'utils/polyfills'],
}
/*
Now, apply the transition.
The transitionOrigin must be applied before the rest of the attributeds, as it may
The transitionOrigin must be applied before the rest of the attributes, as it may
be a function.
*/
if (transitionOrigin) {
......@@ -592,23 +593,36 @@ define(['jquery', 'markup', 'utils/polyfills'],
'animation-duration': transitionTime + "ms",
'animation-delay': (transitionDelay - expedite) + "ms",
});
if (childrenInline) {
/*
If there are no element children of the container (only text), simply use 'inline'.
*/
el.css('display','inline' + (el.children().length ? '-block' : ''));
}
else {
/*
To give newly-created sections time to render and apply changers, such as (box:) to hooks,
this code, which gives the transition element the correct display property, is crudely placed in a delayed timeout.
*/
requestAnimationFrame(() => {
/*
Both of these are necessary for overriding "display:inline-block" for certain transitions,
including those that use transform.
Special case: due to <tw-backdrop> having display:flex, getComputedStyle() will always report 'display:block' for
each child element of it, even if it's a <tw-dialog>. So, ignore childrenProbablyInline() and simply assume that block positioning mustn't be applied.
Note that the other two elements that have display:flex, <tw-story> and <tw-columns>, don't have this concern due to their children
being part of usuallyBlockElements.
*/
el.css({
display:'block !important',
width:"100%",
});
}
if (el.parent().is('tw-backdrop') || Utils.childrenProbablyInline(el)) {
/*
If there are no element children of the container (only text), simply use 'inline'.
*/
el.css('display','inline' + (el.children().length ? '-block' : ''));
}
else {
/*
Both of these are necessary for overriding "display:inline-block" for certain transitions,
including those that use transform.
*/
el.css({
display:'block !important',
width:"100%",
});
}
});
onTransitionComplete(el, transitionTime + transitionDelay - expedite, transitionSkip, (elapsedRealTime) => {
/*
Unwrap the wrapping... unless it contains a non-unwrappable element,
......
Introduction 2: Some of what's new in 3.1
Introduction 2: Welcome to the world of 3.2.0
Harlowe 3.1, ostensibly a minor release of Harlowe, nonetheless has a surprising amount of new features, which are summarised as follows:
Dear fellow author of hypertext fiction: this version of Harlowe is most unlike others before it - despite a commitment to backwards-compatibility letting it be numbered a "minor" release, its wealth of added features is truly incredible. Let me explain in brief how this came to be. In April 2020, as the COVID-19 pandemic death toll was steadily rising worldwide, I came to something of a quiet realisation that it was very possible I would not live to the end of this year, and, furthermore, it was probable that many Harlowe users would not live to the end of this year. I thought about Harlowe, and about how so much functionality I'd always envisioned for it - custom macros, string patterns, fullscreen support - was still unimplemented, and came to a decision that I absolutely had to finally get all of it in, this year.
* New implementations of (alert:), (confirm:) and (prompt:) that should work in more browsers and browser-like environments, and whose dialogs are customisable with CSS.
* The identifiers <a href="#keyword_visits">visits</a> and <a href="#keyword_exits">exits</a>.
* Special data names for <a href="#type_hookname">hooks</a>: `?hook's links`, `?hook's chars` and `?hook's lines`.
* Subarray and substring data names, such as `$arr's 1stto3rd`.
* New macros: (cond:), (nth:), (more:), (passages:) and (gradient:).
* <a href="#markup_unclosed-hook">Unclosed hook markup</a>, a shorthand that you can use with changers.
So, I worked diligently on Harlowe for 9 months. In retrospect, it feels like my life lost all meaning except for Harlowe. But at the end, Harlowe finally became, for the first time, a hypertext fiction coding language I could be proud of. Even now, there's still some extra features I wish I'd been able to fit in, but this version feels like the first version where Harlowe is "complete", as my original vision for the story format in 2014 had resembled.
For a complete list of changes and outlines of how to use the above features, consult the <a href="#changes_3.1.0-changes">change log</a> section.
And now you get to enjoy all of this, and more.
* A means of creating custom macros, using (macro:), (output:) and (output-hook:)
* String patterns, an advanced means of matching strings comprehensively, that can be used with `matches` and the new (split:) and (trimmed:) macros
* Changers for creating block elements, (box:) and (float-box:), with the latter allowing you to overlay a box of text anywhere in the browser window
* Support for storylets as an alternative to hard-coded links between sections of a game, provided by (storylet:), (open-storylets:), and their related macros
* Macros for adding and creating the sidebar's icons, allowing you to freely customise it using (replace:) and other macros
* Several new interaction/UI command macros, such as (input-box:), (checkbox:) and (meter:)
* (set:), (put:) and (move:) now support optional type-restrictions on variables
* A changer and a command for displaying text verbatim, (verbatim:) and (verbatim-print:)
* Interaction macros for toggling fullscreen mode, (link-fullscreen:) and (icon-fullscreen:)
* Assorted pieces of "missing" common functionality, like (rerun:), (link-rerun:), (text-size:), (border:), (opacity:) and (joined:)
* A small selection of debugging-focused macros, such as (ignore:), (test-false:), (test-true:) and (assert:)
* Additional features for Debug Mode, including an enchantments panel, buttons for producing the source code of variables, and a DOM View button that shows the basic HTML DOM structure of the page
* Heavily improved in-editor syntax highlighting that now colours macros by their datatype and can, if enabled, show tooltips for code structures.
And, most importantly, a new toolbar has been implemented for the Twine 2 editor that allows common Harlowe code idioms to be quickly created.
For a complete list of changes and outlines of how to use the above features, consult the <a href="#changes_3.2.0-changes">change log</a> section.
......@@ -126,6 +126,14 @@ tw-broken-link {
border-radius: 0.2em;
}
@mixin button-links {
border-radius: 16px;
border-style: solid;
border-width: 2px;
text-align:center;
padding:0px 8px;
display:block;
}
/*
Button styling is applied in three ways: by (button:) changing normal links and (click:) links as their second argument...
*/
......@@ -135,18 +143,16 @@ tw-link.enchantment-button,
...by link changers and (click:) links being enchanted by (enchant:), or link expressions changed by attached (button:) changers...
*/
.enchantment-button:not(.enchantment-link) > tw-link,
.enchantment-button:not(.enchantment-link) > .enchantment-link,
.enchantment-button:not(.enchantment-link) > .enchantment-link {
@include button-links;
}
/*
...and links inside (dialog:) dialogs.
*/
tw-dialog-links {
padding-top:1.5em;
tw-link {
border-radius: 16px;
border-style: solid;
border-width: 2px;
text-align:center;
@include button-links;
display:inline-block;
padding:0px 8px;
}
}
\ No newline at end of file
......@@ -546,8 +546,8 @@ describe("basic command macros", function() {
expect("(animate:?A,'fade-left',0)").markupToError();
});
it("animates the named hooks with the given animation", function() {
var p = runPassage("|a>[foo]|b>[|a>[foo]](animate:?A,'pulse',9s)");
expect(p.find('tw-hook[data-t8n="pulse"][name="a"]').length).toBe(2);
var p = runPassage("|a>[foo]|b>[|a>[foo]](animate:?A,'pulse',29s)");
expect(p.find('[data-t8n="pulse"] > [name="a"]').length).toBe(2);
});
it("changes the animation-duration with the optional number", function() {
var p = runPassage("|a>[foo](animate:?A,'pulse',9s)");
......
......@@ -52,7 +52,7 @@ describe("debugging macros", function() {
});
it("if given false, it produces an error that shows the call's source", function() {
expect(runPassage("(assert:3<1)").find('tw-error').text()).toContain("(assert:3<1)");
expect(runPassage("(assert:45").find('tw-error').text()).not.toContain("(assert:45)");
expect(runPassage("(assert:45)").find('tw-error').text()).not.toContain("(assert:45)");
});
});
describe("the (assert-exists:) macro", function() {
......
......@@ -473,6 +473,9 @@ describe("data structure macros", function () {
it("doesn't serialise errors", function() {
expect('(source:"red"+2)').markupToError();
});
it("doesn't serialise custom macros' commands", function() {
expect('(set:$a to (macro:[(output:)[]]))(source:($a:))').markupToError();
});
});
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment