diff --git a/dist/macro.min.js b/dist/macro.min.js index 5d94d4b..eaf0db9 100644 --- a/dist/macro.min.js +++ b/dist/macro.min.js @@ -1,3 +1,3 @@ -// Harlowe Macro Framework, by Chapel; version 1.0.0 -;!function(){"use strict";var r={major:1,minor:0,patch:0},e=[r.major,r.minor,r.patch].join(".");r.semantic=e,r=Object.freeze(r);var t=$("tw-storydata"),a=Object.freeze({name:t.attr("name"),ifid:t.attr("ifid")}),n=t.attr("format-version"),o=n.split("."),i=Object.freeze({major:o[0],minor:o[1],patch:o[2],semantic:n});window.Harlowe=window.Harlowe||{},window.Harlowe=Object.assign(window.Harlowe,{framework:r,API_ACCESS:Object.freeze({MACROS:require("macros"),STATE:require("state"),CHANGER:require("datatypes/changercommand"),ENGINE:require("engine")}),engine:i,story:a})}(),function(){"use strict";function e(r){return"number"==typeof r||"boolean"==typeof r||"string"==typeof r||null===r||Array.isArray(r)&&r.every(e)||r instanceof Set&&Array.from(r).every(e)||r instanceof Map&&Array.from(r.values()).every(e)||_changer.isPrototypeOf(r)}window.Harlowe=Object.assign(window.Harlowe,{helpers:{isSerialisable:e,isSerializable:e,arrayify:function(r,e){if(r){var t=[].slice.call(r);return void 0!==e&&(t=t.slice(e)),t}},getPassageData:function(r){var e=$('tw-passagedata[name="'+r+'"]');if(e[0])return e}}})}(),function(){"use strict";var n=window.localStorage||!1,o=Harlowe.story.ifid+"-tw-storage";function r(){try{if(!n)throw new Error("storage is inaccessible");n.setItem(o,JSON.stringify({ifid:Harlowe.story.ifid}))}catch(r){console.warn(r)}}function i(r){try{var e;if(n)return e=JSON.parse(n.getItem(o)),r&&"string"==typeof r?e[r]:e;throw new Error("storage is inaccessible")}catch(r){console.warn(r)}}void 0===i()&&r(),Harlowe.storage={clear:r,save:function(r,e){try{if(!r||"string"!=typeof r)throw new TypeError("cannot store values without a valid storage key");if(void 0===e)throw new TypeError("cannot store undefined values");var t={};if(t[r]=e,!n)throw new Error("storage is inaccessible");var a=i();Object.assign(a,t),n.setItem(o,JSON.stringify(a))}catch(r){console.warn(r)}},load:i,remove:function(r){try{if(!r||"string"!=typeof r)throw new TypeError("cannot store values without a valid storage key");if(!n)throw new Error("storage is inaccessible");var e=i();e.hasOwnProperty(r)&&(delete e[r],n.setItem(o,JSON.stringify(e)))}catch(r){console.warn(r)}}}}(),function(){"use strict";function a(r,e,t){if(!(this instanceof a))return new a(r,e,t);this.name=r||"unknown",this.args=e||[],this.data=t||{},this.type=t&&t.type||"basic",this.fn=t&&t.fn||"handler","changer"===this.type&&("handler"===this.fn?this.instance=t&&t.instance||null:this.descriptor=t&&t.descriptor||null)}a.create=function(r,e,t){if(!r||"string"!=typeof r||!r.trim())throw new TypeError("Invalid macro name.");return e&&e instanceof Array||(e=[]),t&&"object"==typeof t||(t={type:"basic",fn:"handler"}),new a(r,e,t)},Object.assign(a.prototype,{clone:function(){return a.create(this.name,this.args,this.data)},syntax:function(){return"("+this.name+":)"},error:function(r,e){var t="Error in the "+this.syntax()+" macro: "+r;return e&&alert(t),console.warn("HARLOWE CUSTOM MACRO ERROR -> ",t),new Error(r)},typeCheck:function(r){r&&r instanceof Array||(r=Harlowe.helpers.arrayify(arguments));var n=this,o=[];if(r.forEach(function(r,e){var t=e+1,a=[];"string"==typeof r&&("any"===(a=r.includes("|")?r.split("|").map(function(r){return r.trim().toLowerCase()}):[r.trim().toLowerCase()])[0]||a.some(function(r){return typeof n.args[e]===r})||o.push("argument "+t+" should be a(n) "+a.join(" or ")))}),o.length)return n.error(o.join("; "))}}),window.Harlowe=Object.assign(window.Harlowe,{MacroContext:a})}(),function(){"use strict";var c=Harlowe.API_ACCESS.MACROS,f=Harlowe.API_ACCESS.CHANGER;window.Harlowe=Object.assign(window.Harlowe||{},{macro:function(r,e,t){if(!r||"string"!=typeof r||!r.trim())throw new TypeError("Invalid macro name.");if(!e||"function"!=typeof e)throw new TypeError("Invalid macro handler.");var a,n,o,i,s;t&&"function"==typeof t?(o=r,i=e,s=t,c.addChanger(o,function(){var r=Harlowe.helpers.arrayify(arguments,1),e=f.create(o,r),t=Harlowe.MacroContext.create(o,r,{type:"changer",fn:"handler",instance:e});return i.apply(t,r),e},function(){var r=Harlowe.helpers.arrayify(arguments),e=r.shift(),t=Harlowe.MacroContext.create(o,r,{type:"changer",fn:"changer",descriptor:e});s.apply(t,r)},c.TypeSignature.zeroOrMore(c.TypeSignature.Any))):(a=r,n=e,c.add(a,function(){var r=Harlowe.helpers.arrayify(arguments,1),e=Harlowe.MacroContext.create(a,r,{type:"basic",fn:"handler"}),t=n.apply(e,r);return null==t?"":t},c.TypeSignature.zeroOrMore(c.TypeSignature.Any)))}})}(),function(){"use strict";var t=Harlowe.API_ACCESS.STATE,e=Harlowe.API_ACCESS.ENGINE;function a(){return t.passage}window.Harlowe=Object.assign(window.Harlowe||{},{passage:a,tags:function(r){r=r||a();try{var e=Harlowe.helpers.getPassageData(r).attr("tags");return e?e.split(" "):[]}catch(r){return console.warn(r.message),[]}},goto:function(r){return e.goToPassage(r)},variable:function(r,e){if("$"!==r[0])throw new Error('cannot access variable "'+r+'"');if(r=r.substr(1),void 0!==e){if(!Harlowe.helpers.isSerialisable(e))throw new Error('The value passed to variable "'+r+'" cannot be serialized.');t.variables[r]=e}return t.variables[r]},visited:function(r){return t.passageNameVisited(r||a())},hasVisited:function(r){return 0 ",t),new Error(r)},typeCheck:function(r){r&&r instanceof Array||(r=Harlowe.helpers.arrayify(arguments));var n=this,o=[];if(r.forEach(function(r,e){var t=e+1,a=[];"string"==typeof r&&("any"===(a=r.includes("|")?r.split("|").map(function(r){return r.trim().toLowerCase()}):[r.trim().toLowerCase()])[0]||a.some(function(r){return typeof n.args[e]===r})||o.push("argument "+t+" should be a(n) "+a.join(" or ")))}),o.length)return n.error(o.join("; "))}}),window.Harlowe=Object.assign(window.Harlowe,{MacroContext:a})}(),function(){"use strict";var c=Harlowe.API_ACCESS.MACROS,f=Harlowe.API_ACCESS.CHANGER;window.Harlowe=Object.assign(window.Harlowe||{},{macro:function(r,e,t){if(!r||"string"!=typeof r||!r.trim())throw new TypeError("Invalid macro name.");if(!e||"function"!=typeof e)throw new TypeError("Invalid macro handler.");var a,n,o,i,s;t&&"function"==typeof t?(o=r,i=e,s=t,c.addChanger(o,function(){var r=Harlowe.helpers.arrayify(arguments,1),e=f.create(o,r),t=Harlowe.MacroContext.create(o,r,{type:"changer",fn:"handler",instance:e});return i.apply(t,r),e},function(){var r=Harlowe.helpers.arrayify(arguments),e=r.shift(),t=Harlowe.MacroContext.create(o,r,{type:"changer",fn:"changer",descriptor:e});s.apply(t,r)},c.TypeSignature.zeroOrMore(c.TypeSignature.Any))):(a=r,n=e,c.add(a,function(){var r=Harlowe.helpers.arrayify(arguments,1),e=Harlowe.MacroContext.create(a,r,{type:"basic",fn:"handler"}),t=n.apply(e,r);return null==t?"":t},c.TypeSignature.zeroOrMore(c.TypeSignature.Any)))}})}(),function(){"use strict";var t=Harlowe.API_ACCESS.STATE,e=Harlowe.API_ACCESS.ENGINE;function a(){return t.passage}window.Harlowe=Object.assign(window.Harlowe||{},{passage:a,tags:function(r){r=r||a();try{var e=Harlowe.helpers.getPassageData(r).attr("tags");return e?e.split(" "):[]}catch(r){return console.warn(r.message),[]}},goto:function(r){return e.goToPassage(r)},variable:function(r,e){if("$"!==r[0])throw new Error('cannot access variable "'+r+'"');if(r=r.substr(1),void 0!==e){if(!Harlowe.helpers.isSerialisable(e))throw new Error('The value passed to variable "'+r+'" cannot be serialized.');t.variables[r]=e}return t.variables[r]},visited:function(r){return t.passageNameVisited(r||a())},hasVisited:function(r){return 0>` macro. - - `(macro:)` A widget-style macro for Harlowe by rachek64. + - `(macro:)` A widget-style macro passage system for Harlowe by rachek64. - **[Docs]** Documented example macros. ### v1.0.0 diff --git a/docs/download/fetch.js b/docs/download/fetch.js index cc6beda..9086350 100644 --- a/docs/download/fetch.js +++ b/docs/download/fetch.js @@ -13,6 +13,7 @@ 'Articles' : 'articles', 'Clamp': 'clamp', 'Dialog' : 'dialog+css', + 'Notify' : 'notify+css', 'Dice': 'dice', 'Hotkeys': 'hotkeys', 'Playtime': 'playtime', diff --git a/docs/examples/macro-passages.md b/docs/examples/macro-passages.md index a57ab1e..0b824af 100644 --- a/docs/examples/macro-passages.md +++ b/docs/examples/macro-passages.md @@ -2,11 +2,74 @@ By [rachek64](https://github.com/rachek64). -Description. +Allows you to run passages inline, similar to `(display:)`, but with no output and with the option of passing arguments to the passage, similar to SugarCube's widgets. > **Get the Code** > > - [Minified](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/third-party/minified/macro-passages.min.js) > - [Pretty](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/third-party/macro-passages.js) -Coming soon. \ No newline at end of file +### Macro: `(macro:)` + +Runs a macro passage, defined by adding the `macro` tag to a passage. If you add both the `macro` tag and the `auto-macro` tag, a first-class, named macro will be created. + +#### Syntax + +``` +(macro: passage [, argsList]) +``` + +#### Arguments + +- `passage` ( *`string`* ) The name of a passage with the tag `macro`. The passage's code will be run with the given arguments. +- `args` ( *`any`* ) ( optional ) Arguments to be passed on to the passage, which may be accessed as an array using the temporary variable `_args`. Just list arguments separated by commas after the passage name. + +#### Returns + +Optionally returns the value given to the `_result` temporary variable, or nothing. + +#### Examples + +``` +:: damage [macro] + +(set: $player's hp to it - _args's 1st) +(if: $player's hp <= 0)[ + (goto: 'game over') +] + +:: some passage +The enemy attacks! +(macro: 'damage', $enemy's attack) +You take (print: $enemy's attack) damage and have (print: $player's hp) health remaining. + +:: say-hi [macro auto-macro] + +(set: _result to 'Hi, ' + _args's 1st + '.') + +:: some other passage +(alert: (say-hi: 'Bob')) +``` + +### Additional Usage Notes + +* The entire macro-tagged passage should run at once (i.e., no `(prompt:)`, `(live:)`, or similar "delayed" commands) and they cannot display any output. This is suitable for code-only macros that perform complex calculations or automate repetitive tasks. If you need to print results, just return the results from the macro with `_result` and print it outside. +* No text is displayed while running the macro passage, nor can it have any +direct effect on the caller passage. The contents are rendered into a +dummy screen that is never shown on the page. +* This means you can nicely comment your macro with plain text. No need to use `{}` + or worry about line breaks or spacing. +* This also means no user input is possible. If you render a link to click, + it will never be able to be clicked. The macro will complete assuming the link + wasn't clicked. +* User input in general is not possible in macro-tagged passages, nor can you run +asynchronous commands like (alert:) that pause the passage until you interact +with them. No errors will happen, but the value in `_result` may not be +picked up properly if it was changed after the delaying command was used. +* Saving and loading from inside a macro is, in general, a terrible idea. Just don't do it. +* Argument type checking is not possible at this time. If a macro takes arguments, +thoroughly inspect `_args` before attempting to use them. +* Unfortunately, errors in macro passages are handled a bit oddly. Error details +are provided as best as possible, but so far as Harlowe is concerned the error +occurred in the caller passage. Check the dropdown in the error bubble for the +actual line that triggered the error. \ No newline at end of file diff --git a/docs/examples/notify.md b/docs/examples/notify.md index a49bad3..f66dcfa 100644 --- a/docs/examples/notify.md +++ b/docs/examples/notify.md @@ -1,6 +1,6 @@ ## Notify -Description. +Creates a quick user notification that rolls out from the top right of the screen, hangs for a minute, and rolls back in, useful for a quick note about status updates, achievements, or whatever else when a dialog box is a bit too much. Ported over from the similar CMFSC2 macro. > - **Get the Code** > @@ -10,4 +10,38 @@ Description. > - [Minified](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/minified/notify.min.css) > - [Pretty](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/notify.css) -Coming soon. \ No newline at end of file +### Macro: `(notify:)` + +Creates and displays a quick notification and renders the content of the associated hook into it. + +#### Syntax + +``` +(notify: [delay] [, classList])[ ... ] +``` + +#### Arguments + +- `delay` ( *`time`* ) ( optional ) A CSS-style time value (e.g., `5s`, `500ms`) for controlling how long the notification should remain. A short animation plays before and after this delay. Defaults to two seconds. +- `classList` ( *`string`* | *`string array`* ) ( optional ) You may pass a single string of space separated class names (e.g. `"my-class another-class a-third-class"`), an array of strings, or simply list several different strings as arguments to the macro to have them added to the `#notify` element for styling. You must include a delay time to add classes + +#### Returns + +Nothing. + +#### Examples + +``` + +(notify:)[Inventory updated.] + + +(notify: '10s', 'my-class')[Achievement unlocked.] + + +(notify: '5000ms')[Gold earned.] +``` + +#### Note + +Giving the player unlimited control over these notifications, or trying to show several at once or right after each other will cause them to trip over themselves as they try to animate, so try to keep them spaced out, and don't assign them to links or buttons you expect the player to press repeatedly. \ No newline at end of file diff --git a/examples/call.js b/examples/call.js deleted file mode 100644 index dfeb405..0000000 --- a/examples/call.js +++ /dev/null @@ -1,82 +0,0 @@ -(function () { - // call and reply macro set, v1.0.0 - 'use strict'; - - var eventNameSpace = '.userland'; - var eventPrefix = ':tw-call-macro-'; - - function isDef (is) { - return is != undefined; - } - - function evType (type) { - return eventPrefix + String(type) + eventNameSpace; - } - - function setCall (type, args, test) { - if (isDef(args) && isDef(test)) { - args = Harlowe.helpers.arrayify(arguments, 2); - } else if (isDef(args) && !(args instanceof Array)) { - args = [args]; - } - - $(document).trigger({ - type : evType(type), - args : isDef(args) ? args : undefined - }); - } - - function removeCall (type) { - $(document).off(evType(type)); - } - - function reply (type, cb, once, context) { - $(document)[ once ? 'one' : 'on' ](evType(type), function (ev) { - var shallow = Harlowe.variable('args'), deep; - if (shallow instanceof Map) { - deep = new Map(shallow.entries()); - } else if (shallow instanceof Set) { - deep = new Set(shallow.entries()); - } else if (typeof shallow === 'object') { - deep = JSON.parse(JSON.stringify(shallow)); - } else { - deep = shallow; - } - - Harlowe.variable('args', ev.args); - // callback - cb.call(context); - Harlowe.variable('args', deep); - }); - } - - Harlowe.macro('call', function (type, args) { - var err = this.typeCheck([ 'string' ]); - if (err) throw err; - - setCall.apply(null, arguments); - }); - - Harlowe.macro('reply', function (type, once) { - - var err = this.typeCheck([ 'string', 'boolean|undefined' ]); - if (err) throw err; - - }, function (type, once) { - - this.descriptor.enabled = false; - - reply(type, function () { - this.enabled = true; - }, !!once, this.descriptor); - - }); - - Harlowe.macro('silence', function (type) { - var err = this.typeCheck([ 'string' ]); - if (err) throw err; - - removeCall(type); - }); - -}()); \ No newline at end of file diff --git a/package.json b/package.json index 627d2ec..ebfbef7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "harlowe-macro-api", - "version": "1.0.0", + "version": "1.0.1", "description": "Unofficial Macro API for Twine2/Harlowe.", "main": "build.js", "dependencies": {},