Commit e512d31b authored by Leon Arnott's avatar Leon Arnott
Browse files

Fixed a bug that caused certain event handlers to not be attached to...

Fixed a bug that caused certain event handlers to not be attached to <tw-story>, making (dropdown:) stop working.

Also, added checks to the (alert:), (prompt:) and (confirm:) macros so that they produce normal TwineErrors if they run on a browser with prompt(), alert() or confirm() removed.

Also tweaked the CSS for <select> elements somewhat.

Closes #165. Closes #166.
parent ad761f87f986
......@@ -2,11 +2,13 @@
Rough documentation is at http://twine2.neocities.org/. See below for compilation instructions.
###3.0.2 changes:
###3.0.2 changes (unreleased):
###Bugfixes
* Fixed the (str:) macro alias added in 3.0.0 mysteriously not actually having been added.
* Fixed a startup bug that potentially caused `(dropdown:)` menus to stop affecting their bound variables for the rest of the game.
* Now, `(alert:)`, `(prompt:)` and `(confirm:)` produce errors if they are used in a browser tha doesn't support Javascript's `prompt()`, `alert()` or `confirm()` functions, instead of crashing the page.
* Fixed the `(str:)` macro alias added in 3.0.0 mysteriously not actually having been added.
###3.0.1 changes:
......
......@@ -30,7 +30,7 @@ define(['utils', 'utils/operationutils', 'internaltypes/varscope', 'internaltype
* "when" lambdas are a variant of "where" used exclusively by (event:), and are used to specify a live event when a hook should
be shown. The lambda `when $fuel > 8` tells (event:) to show the attached hook when `$fuel` is increased (due to an interaction macro
like (click-repeat:), a (live:) macro, or anything else). This really shouln't be called a "lambda", but you can perhaps think of it in
like (link-repeat:), a (live:) macro, or anything else). This really shouln't be called a "lambda", but you can perhaps think of it in
terms of it filtering moments in time that pass or fail the condition.
Lambdas use temp variables as "placeholders" for the actual values. For instance, in `(find: _num where _num > 2, 5,6,0)`,
......
......@@ -22,10 +22,10 @@ require.config({
'jqueryplugins',
],
});
require(['jquery', 'debugmode', 'renderer', 'state', 'engine', 'passages', 'utils/selectors', 'macros',
require(['jquery', 'debugmode', 'renderer', 'state', 'engine', 'passages', 'utils', 'utils/selectors', 'macros',
'macrolib/values', 'macrolib/commands', 'macrolib/datastructures', 'macrolib/stylechangers', 'macrolib/enchantments', 'macrolib/links',
'repl'],
($, DebugMode, Renderer, State, Engine, Passages, Selectors) => {
($, DebugMode, Renderer, State, Engine, Passages, Utils, Selectors) => {
/*
Harlowe, the default story format for Twine 2.
......@@ -90,7 +90,7 @@ require(['jquery', 'debugmode', 'renderer', 'state', 'engine', 'passages', 'util
/*
This is the main function which starts up the entire program.
*/
$(() => {
Utils.onStartup(() => {
const header = $(Selectors.storyData);
if (header.length === 0) {
......
"use strict";
define(['jquery', 'requestAnimationFrame', 'macros', 'utils', 'utils/selectors', 'state', 'passages', 'engine', 'internaltypes/twineerror', 'datatypes/hookset', 'datatypes/varbind', 'utils/operationutils'],
($, requestAnimationFrame, Macros, {storyElement, toJSLiteral, unescape}, Selectors, State, Passages, Engine, TwineError, HookSet, VarBind, {printBuiltinValue}) => {
($, requestAnimationFrame, Macros, Utils, Selectors, State, Passages, Engine, TwineError, HookSet, VarBind, {printBuiltinValue}) => {
/*d:
Command data
......@@ -75,7 +75,7 @@ define(['jquery', 'requestAnimationFrame', 'macros', 'utils', 'utils/selectors',
);
}
},
(cd, _, name) => assign(cd, { source: unescape(Passages.get(name).get('source')) }),
(cd, _, name) => assign(cd, { source: Utils.unescape(Passages.get(name).get('source')) }),
[String])
/*d:
......@@ -378,7 +378,7 @@ define(['jquery', 'requestAnimationFrame', 'macros', 'utils', 'utils/selectors',
which is implemented similar to the link macros - the ChangeDescriptor's data.dropdownEvent indicates
what to do when the <select> is interacted with.
*/
$(() => $(storyElement).on(
Utils.onStartup(() => $(Utils.storyElement).on(
/*
The jQuery event namespace is "dropdown-macro".
*/
......@@ -686,6 +686,12 @@ define(['jquery', 'requestAnimationFrame', 'macros', 'utils', 'utils/selectors',
the player's operating system and browser's styling, it may clash visually with the design
of your story.
On recent versions of <a href="https://electronjs.org/">Electron</a>, the browser-like
program used to run hard-coded HTML files as native apps,
the Javascript `alert()` function **may not be usable**, thus causing this command to produce an error.
Thus, you may not be able to run a Harlowe game in Electron using this macro. Future versions of Harlowe
may remove this restriction.
When the dialog is on-screen, the entire game is essentially "paused" - no further computations are
performed until it is dismissed.
......@@ -697,7 +703,11 @@ define(['jquery', 'requestAnimationFrame', 'macros', 'utils', 'utils/selectors',
#popup
*/
("alert",
() => {},
() => {
if (typeof window.alert !== "function") {
return TwineError.create("unimplemented","The (alert:) macro doesn't seem to work in this browser. Sorry.");
}
},
(/* no cd because this is attachable:false */ _, text) => {
window.alert(text);
},
......@@ -933,6 +943,12 @@ define(['jquery', 'requestAnimationFrame', 'macros', 'utils', 'utils/selectors',
But, be aware that as the box uses the player's operating system and browser's styling, it
may clash visually with the design of your story.
On recent versions of <a href="https://electronjs.org/">Electron</a>, the browser-like program
used to run hard-coded HTML files as native apps,
the Javascript `prompt()` function **may not be usable**, thus causing this command to produce an error.
Thus, you may not be able to run a Harlowe game in Electron using this macro. Future versions of Harlowe
may remove this restriction.
When the dialog is on-screen, the entire game is essentially "paused" - no further computations are
performed until it is dismissed.
......@@ -944,7 +960,12 @@ define(['jquery', 'requestAnimationFrame', 'macros', 'utils', 'utils/selectors',
#popup
*/
("prompt",
(_, text, value) => window.prompt(text, value) || "",
(_, text, value) => {
if (typeof window.prompt !== "function") {
return TwineError.create("unimplemented","The (prompt:) macro doesn't seem to work in this browser. Sorry.");
}
return window.prompt(text, value) || "";
},
[String, String])
/*d:
......@@ -963,6 +984,12 @@ define(['jquery', 'requestAnimationFrame', 'macros', 'utils', 'utils/selectors',
But, be aware that as the box uses the player's operating system and browser's styling, it
may clash visually with the design of your story.
On recent versions of <a href="https://electronjs.org/">Electron</a>, the browser-like program
used to run hard-coded HTML files as native apps,
the Javascript `confirm()` function **may not be usable**, thus causing this command to produce an error.
Thus, you may not be able to run a Harlowe game in Electron using this macro. Future versions of Harlowe
may remove this restriction.
When the dialog is on-screen, the entire game is essentially "paused" - no further computations are
performed until it is dismissed.
......@@ -974,7 +1001,12 @@ define(['jquery', 'requestAnimationFrame', 'macros', 'utils', 'utils/selectors',
#popup
*/
("confirm",
(_, text) => window.confirm(text),
(_, text) => {
if (typeof window.confirm !== "function") {
return TwineError.create("unimplemented","The (confirm:) macro doesn't seem to work in this browser. Sorry.");
}
return window.confirm(text);
},
[String])
/*d:
......
......@@ -284,7 +284,7 @@ define(['jquery', 'utils', 'utils/selectors', 'utils/operationutils', 'engine',
is "attached" via a jQuery .data() key, and must be called
from this <tw-story> handler.
*/
$(() => {
Utils.onStartup(() => {
Utils.storyElement.on(
/*
Put this event in the "enchantment" jQuery event
......@@ -469,7 +469,7 @@ define(['jquery', 'utils', 'utils/selectors', 'utils/operationutils', 'engine',
/*
A separate click event needs to be defined for .enchantment-clickblock, which is explained below.
*/
$(() => {
Utils.onStartup(() => {
Utils.storyElement.on(
/*
Put this event in the "enchantment" jQuery event namespace, alongside the other enchantment events.
......
......@@ -23,7 +23,7 @@ define(['jquery', 'macros', 'utils', 'utils/selectors', 'state', 'passages', 'en
is "attached" via a jQuery .data() key, and must be called
from this <tw-story> handler.
*/
$(() => $(Utils.storyElement).on(
Utils.onStartup(() => $(Utils.storyElement).on(
/*
The jQuery event namespace is "passage-link".
*/
......
......@@ -47,7 +47,7 @@ define(['jquery','macros', 'utils', 'utils/selectors', 'datatypes/colour', 'data
The (hover-style:) macro uses these jQuery events to add and remove the
hover styles when hovering occurs.
*/
$(() => $(Utils.storyElement).on(
Utils.onStartup(() => $(Utils.storyElement).on(
/*
The jQuery event namespace is "hover-macro".
*/
......
"use strict";
define(['jquery', 'utils', 'utils/selectors'], ($, {unescape}, Selectors) => {
define(['jquery', 'utils', 'utils/selectors'], ($, {unescape,onStartup}, Selectors) => {
/*
Passages
A userland registry of Passage objects.
......@@ -75,7 +75,7 @@ define(['jquery', 'utils', 'utils/selectors'], ($, {unescape}, Selectors) => {
Unfortunately, the DOM isn't visible until the page is loaded, so we can't
read every <tw-passagedata> from the <tw-storydata> HTML and store them in Passages until then.
*/
$(() => {
onStartup(() => {
Array.from($(Selectors.storyData + " > " + Selectors.passageData)).forEach(e => {
e = $(e);
Passages.set(e.attr('name'), new Passage(e));
......
......@@ -55,7 +55,13 @@ define(['jquery', 'markup', 'utils/selectors', 'utils/polyfills'],
let
//A binding for the cached <tw-story> reference (see below).
storyElement;
storyElement,
/*
An array of functions to run only after page load. This is set to null
once page load has completed, causing onStartup() to just run the passed function
immediately.
*/
startupCallbacks = [];
/*
A static class with helper methods used throughout Harlowe.
......@@ -517,6 +523,17 @@ define(['jquery', 'markup', 'utils/selectors', 'utils/polyfills'],
}
},
/*
Other modules which need to run startup initialisers involving the <tw-story> element
can register them with Utils, so that they're run only on starting up.
*/
onStartup(fn) {
if (startupCallbacks) {
startupCallbacks.push(fn);
}
else fn();
},
/*
Constants
*/
......@@ -527,6 +544,7 @@ define(['jquery', 'markup', 'utils/selectors', 'utils/polyfills'],
get storyElement() {
return storyElement;
},
};
/*
......@@ -534,7 +552,14 @@ define(['jquery', 'markup', 'utils/selectors', 'utils/polyfills'],
used even when it is disconnected from the DOM (which occurs when a new
passage is being rendered into it).
*/
$(()=> storyElement = $(Selectors.story));
$(() => {
storyElement = $(Selectors.story);
// Run all the callbacks registered in startupCallbacks by other modules.
startupCallbacks.forEach(e => e());
// Release startupCallbacks, causing onStartup() to no longer register functions,
// instead just calling them immediately.
startupCallbacks = null;
});
return Object.freeze(Utils);
});
......@@ -198,7 +198,7 @@ select {
&:not([disabled]) {
color: inherit;
}
background-color: inherit;
background-color: transparent;
font:inherit;
border-style:solid;
padding: 2px;
......
......@@ -138,6 +138,7 @@ describe("interface macros", function(){
p = runPassage("(dropdown: bind $foo, 'bar','baz', 'qux')");
p.find('select').val('baz').change();
setTimeout(function() {
expect(p.text()).toBe('barbazqux');
expect("$foo").markupToPrint('baz');
p = runPassage("(dropdown: bind $foo, 'bar','baz', 'qux')");
......@@ -148,10 +149,44 @@ describe("interface macros", function(){
});
});
});
it("works with (link-replace:)", function(done) {
var p = runPassage("(dropdown: bind $bar, '1', '2', '3')|baz>[$bar](link-repeat:'foo')[(replace: ?baz)[$bar]]");
p.find('select').val('2').change();
p.find('tw-link').click();
setTimeout(function() {
expect(p.text()).toBe("1232foo");
done();
},300);
});
it("errors if the first or last labels are empty", function() {
expect("(dropdown: bind $foo, '','baz','qux')").markupToError();
expect("(dropdown: bind $foo, 'foo','baz','')").markupToError();
expect("(dropdown: bind $foo, '','baz','')").markupToError();
});
it("has the correct text colour", function(done) {
var p = runPassage("(dropdown: bind $foo, 'bar','','くりかえす')");
setTimeout(function() {
expect(p.find('select').css('color')).toMatch(/(?:#FFF(?:FFF)?|rgb\(\s*255,\s*255,\s*255\s*\))/);
p = runPassage("(enchant: ?passage, (background:'#0000FF')+(text-color:'#800000'))(dropdown: bind $foo, 'bar','','くりかえす')");
setTimeout(function() {
expect(p.find('select').css('color')).toMatch(/(?:#800000|rgb\(\s*128,\s*0,\s*0\s*\))/);
done();
});
});
});
it("has the correct background colour", function(done) {
var p = runPassage("(enchant: ?passage, (background:'#0000FF')+(text-color:'#800000'))(dropdown: bind $foo, 'bar','','くりかえす')");
setTimeout(function() {
expect(p.find('select').css('background-color')).toMatch(/transparent/);
done();
});
});
it("has the correct font", function(done) {
var p = runPassage("(enchant: ?passage, (font:'fantasy'))(dropdown: bind $foo, 'bar','','くりかえす')");
setTimeout(function() {
expect(p.find('select').css('font-family')).toBe('fantasy');
done();
});
});
});
});
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